Last active
March 28, 2024 14:25
-
-
Save scztt/f9e0fd2e01a21416fb89a97eb944d0b2 to your computer and use it in GitHub Desktop.
This file contains 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
PparStream : Stream { | |
var <>initStreamAction, <>endStreamAction; | |
var priorityQ, <now; | |
var <injectFunc, pausedStreams; | |
*forkPatterns { | |
|pattern| | |
var event, outerEvent, recursionLevel, instrument, embeddingLevel, freq, rest; | |
var args, defaults, timingOffset, sustain, gatePattern; | |
var size, newPatterns = []; | |
if(pattern.notNil) { | |
// [1] Preserve information from outer pattern, but not delta. | |
if(~transparency ? true) { | |
outerEvent = currentEnvironment.copy; | |
outerEvent.putPairs( | |
[ | |
\pattern, \type, | |
\parentType, \addToCleanup, | |
\removeFromCleanup, \sustain, | |
\legato, \timingOffset, \delta, \gatePattern | |
].collect([_, nil]).flatten | |
) | |
} { | |
outerEvent = Event.default.copy; | |
}; | |
// [2] If a function, fill in it's args from the environment | |
if (pattern.isKindOf(Function)) { | |
defaults = pattern.def.prototypeFrame; | |
args = pattern.def.argNames.collect { | |
|name, i| | |
currentEnvironment[name].value ?? { defaults[i] } | |
}; | |
pattern = pattern.value(*args); | |
}; | |
// [3] Grab timing properties | |
sustain = ~sustain.value; | |
~timingOffset = timingOffset = (~timingOffset.value ? 0); | |
~gatePattern = gatePattern = (~gatePattern.value ? true); | |
// [4] Apply flop | |
if (~flop ? false) { | |
outerEvent = outerEvent.asPairs.flop.collect(_.asEvent); | |
outerEvent.do(_.parent_(currentEnvironment.parent)); | |
} { | |
outerEvent = [outerEvent] | |
}; | |
// [5] Process inner pattern | |
outerEvent.do { | |
|outerEvent, i| | |
var innerPattern; | |
outerEvent.use { | |
innerPattern = pattern; | |
if (innerPattern.isKindOf(Symbol)) { | |
innerPattern = Pdef(pattern); | |
}; | |
if (innerPattern.isKindOf(Stream) and: { outerEvent.size > 1 }) { | |
innerPattern = innerPattern.copy; | |
}; | |
if (innerPattern.isKindOf(Event)) { | |
Error("Event patterns must be wrapped in a Ref or a function when passed in as an \\instrument argument").throw; | |
}; | |
if (innerPattern.isKindOf(PatternProxy)) { | |
innerPattern = innerPattern.pattern; // optimization. outer pattern takes care for replacement | |
}; | |
if (innerPattern.notNil) { | |
// not sure why we DON'T need to account for positive timingOffset here, | |
// but if we do it breaks.... | |
if (gatePattern) { | |
innerPattern = innerPattern.finDur(sustain - timingOffset.min(0) - 0.000000001); | |
}; | |
if (not(~filter ? false)) { | |
innerPattern = Pevent(innerPattern, outerEvent).asStream; | |
}; | |
if (timingOffset < 0) { | |
innerPattern.fastForward(timingOffset.neg, 0, outerEvent); | |
}; | |
newPatterns = newPatterns.add([ | |
timingOffset, innerPattern | |
]); | |
} | |
} | |
} | |
}; | |
^newPatterns | |
} | |
*initClass { | |
Class.initClassTree(Event); | |
Event.addEventType(\fork, { | |
this.forkPatterns(~pattern ?? { ~instrument }).do { | |
|newPatterns| | |
~injectStream.value( | |
delta: newPatterns[0], | |
stream: newPatterns[1], | |
inval: newPatterns[2] | |
) | |
} | |
}, Event.parentEvents.default.copy.putAll((legato:1))); | |
} | |
*new { | |
^super.new.init | |
} | |
init { | |
priorityQ = PriorityQueue(); | |
pausedStreams = IdentitySet(); | |
now = 0; | |
injectFunc = { |delta, stream, inval| this.injectStream(delta, stream, inval) }; | |
} | |
reset { | |
this.init(); | |
} | |
injectStream { | |
|delta, stream, inval| | |
stream = stream.asStream; | |
// "injecting stream %".format(stream.identityHash).postln; | |
initStreamAction !? { | |
stream = initStreamAction.value(stream, inval).asStream | |
}; | |
priorityQ.put(now + delta, stream); | |
^stream; | |
} | |
pauseStream { | |
|stream| | |
pausedStreams = pausedStreams.add(stream); | |
} | |
resumeStream { | |
|stream, delta=0| | |
pausedStreams = pausedStreams.remove(stream); | |
} | |
injectEvent { | |
|delta, event, inval| | |
this.injectStream( | |
delta, | |
Routine({ event.yield; nil.yield }, 2), | |
inval | |
) | |
} | |
// processStreamsToPause { | |
// streamsToPause.do { | |
// |stream| | |
// priorityQ.remove(stream); | |
// pausedStreams = pausedStreams.add(stream); | |
// }; | |
// streamsToPause.clear(); | |
// } | |
embedInStream { | |
|inval, duration| | |
var stream, nextTime, outval, endTime, nextTimeDelta; | |
endTime = now + (duration ? inf); | |
while { priorityQ.notEmpty and: { now < endTime } } { | |
nextTime = min( | |
priorityQ.topPriority, | |
endTime | |
); | |
// "now: % nextTime: % endTime: %".format(now, nextTime, endTime).postln; | |
if (nextTime > now) { | |
nextTimeDelta = nextTime - now; | |
pausedStreams.do { | |
|stream| | |
priorityQ.put(nextTimeDelta, stream); | |
}; | |
inval = Event.silent(nextTimeDelta).yield; | |
now = nextTime; | |
} { | |
stream = priorityQ.pop; | |
if (pausedStreams.includes(stream).not) { | |
outval = stream.next(inval).asEvent; | |
if (outval.notNil) { | |
// requeue stream | |
priorityQ.put(now + outval.delta, stream); | |
outval[\delta] = 0; | |
outval[\injectStream] = outval[\injectStream] ?? { injectFunc }; | |
inval = outval.yield; | |
} { | |
endStreamAction.value(stream, inval); | |
} | |
}; | |
} | |
}; | |
if (duration.notNil and: { now < endTime }) { | |
inval = Event.silent(endTime - now).put(\debug, "PparStream(%):embedInStream:207".format(this.identityHash)).yield; | |
}; | |
^inval | |
} | |
} | |
Ppar2 : ListPattern { | |
var <>initStreamAction, <>endStreamAction; | |
embedInStream { | |
|inval| | |
var parStream = PparStream() | |
.initStreamAction_(initStreamAction) | |
.endStreamAction_(endStreamAction); | |
repeats.value(inval).do({ | |
list.do(parStream.injectStream(0, _, inval)); | |
parStream.embedInStream(inval); | |
parStream.reset(); | |
}) | |
} | |
} | |
PwithStream : Stream { | |
var <>parStream, <>inputStream, <>replacementStream; | |
var routine; | |
// *new { | |
// |parStream, inputStream, condition, patternFunction, replace, gate, insert| | |
// ^super.newCopyArgs(parStream, inputStream, condition, patternFunction, replace, gate, insert).init | |
// } | |
*new { | |
|parStream, inputStream, replacementStream| | |
^super.newCopyArgs(parStream, inputStream, replacementStream).init | |
} | |
init { | |
routine = Routine({ | |
|inEvent| | |
this.embedInStream(inEvent); | |
}); | |
} | |
next { | |
|inEvent| | |
^routine.next(inEvent); | |
} | |
embedInStream { | |
|inEvent| | |
var originalEventDelta; | |
var inEvents, outputStream, eventFilters; | |
outputStream = Routine({ | |
|inEvent| | |
var shouldPlayOriginalEvent; | |
var shouldWaitForForkedStream; | |
var shouldReplaceAll; | |
var shouldGatePattern; | |
var shouldFilterEvents; | |
var forkPatterns=[], insertPatterns=[], resultPatterns, inputFilters; | |
var outEvent, outDelta, outReplacement, skipRests = false; | |
while { | |
while { | |
outEvent = inputStream.next(inEvent); | |
skipRests and: { outEvent.isRest } | |
}; | |
skipRests = false; | |
outEvent.notNil | |
} { | |
shouldPlayOriginalEvent = true; | |
shouldWaitForForkedStream = false; | |
shouldReplaceAll = false; | |
shouldGatePattern = nil; | |
shouldFilterEvents = false; | |
if (outEvent.isRest.not and: { | |
outEvent.use { | |
///////////////////////////////////////////////////////////////////////////////// | |
outReplacement = replacementStream.next(outEvent); | |
///////////////////////////////////////////////////////////////////////////////// | |
}; | |
outReplacement.notNil | |
}) { | |
shouldPlayOriginalEvent = false; | |
shouldWaitForForkedStream = false; | |
shouldReplaceAll = false; | |
outEvent.copy.use { | |
// SUBTLE: We want to prevent gating if we're doing an insert, | |
// but we only know if we're doing an insert once we've run our function. | |
shouldGatePattern = ~gatePattern ?? { true }; | |
~gatePattern = { | |
if (~insert !? _.value ?? false) { | |
false | |
} { | |
shouldGatePattern.value | |
} | |
}; | |
///////////////////////////////////////////////////////////////////////////////// | |
resultPatterns = PparStream.forkPatterns(outReplacement); | |
///////////////////////////////////////////////////////////////////////////////// | |
shouldFilterEvents = ~filter ?? { shouldFilterEvents }; | |
shouldWaitForForkedStream = ~insert ?? { shouldWaitForForkedStream }; | |
shouldReplaceAll = ~replaceAll ?? { shouldWaitForForkedStream }; | |
shouldPlayOriginalEvent = ( | |
resultPatterns.isEmpty | |
or: { shouldFilterEvents } | |
// or: { shouldWaitForForkedStream.postln } | |
or: { | |
~replace !? _.not ?? { shouldPlayOriginalEvent } | |
} | |
); | |
if (shouldFilterEvents) { | |
resultPatterns.do { | |
|pattern| | |
inputFilters = inputFilters.add(pattern[1].asStream); | |
} | |
}; | |
if (resultPatterns.isEmpty.not && shouldReplaceAll) { | |
forkPatterns.extend(0); | |
insertPatterns.extend(0); | |
}; | |
if (shouldFilterEvents.not) { | |
if (shouldWaitForForkedStream) { | |
insertPatterns = insertPatterns.addAll(resultPatterns) | |
} { | |
forkPatterns = forkPatterns.addAll(resultPatterns) | |
} | |
} | |
}; | |
}; | |
////////////////////////////////////////////////////////////////////////////////////////// | |
inputFilters.copy.do { | |
|filter| | |
var newEvent = filter.next(outEvent); | |
if (newEvent.isNil) { | |
inputFilters.remove(filter) | |
} { | |
outEvent = newEvent; | |
} | |
}; | |
////////////////////////////////////////////////////////////////////////////////////////// | |
// Process all input events at the current time BEFORE forking / inserting | |
if (outEvent.delta > 0) { | |
// If we generated no works, then yield straight through | |
// First, inject our forked streams | |
forkPatterns !? { | |
|toFork| | |
toFork = toFork.copy(); | |
forkPatterns.extend(0); | |
toFork.do { | |
|p| | |
// "parStream.injectStream(%, %, %);".format(p[0], p[1], outEvent).postln; | |
parStream.injectStream(p[0], p[1], outEvent); | |
} | |
}; | |
if (insertPatterns.size > 0) { | |
insertPatterns !? { | |
|toInsert| | |
var oldDelta; | |
// TODO: This means the next event occurs RIGHT after this insert ends - do we want this? | |
if (shouldPlayOriginalEvent) { | |
// // @SUBTLE - If we're replacing the original event, then the overall duration of our inserted | |
// // pattern is the "new" duration. | |
// oldDelta = outEvent.delta; | |
// toInsert = toInsert.add([ | |
// 0, | |
// Event.rest(oldDelta) | |
// ]); | |
"playing original event through insert".postln; | |
inEvent = outEvent.copy.put(\delta, 0).put(\debug, 387).yield; | |
}; | |
toInsert = toInsert.flatten; | |
insertPatterns.extend(0); | |
inEvent = Ptpar(toInsert).trace.embedInStream(outEvent); | |
skipRests = true; | |
}; | |
} { | |
if (shouldPlayOriginalEvent) { | |
inEvent = outEvent.yield; | |
} { | |
inEvent = Event.silent(outEvent.delta).put(\debug, 406).yield | |
} | |
} | |
} { | |
if (shouldPlayOriginalEvent) { | |
inEvent = outEvent.yield; | |
} { | |
inEvent = Event.silent(outEvent.delta).put(\debug, 413).yield | |
} | |
} | |
} | |
}); | |
parStream.injectStream(0, outputStream, inEvent); | |
^parStream.embedInStream(inEvent) | |
} | |
} | |
Pwith : Pattern { | |
// var <inputPattern, <conditions, <patternFunctions, | |
var inputPattern, replacements, <>replace, <>gate, <>insert; | |
*new { | |
|inputPattern ...replacements| | |
^super.newCopyArgs(inputPattern, replacements ?? {[]}) | |
} | |
*conditions { | |
|inputPattern, condition, pattern ...conditionPatterns| | |
conditionPatterns = ([condition, pattern] ++ conditionPatterns).clump(2); | |
conditionPatterns = conditionPatterns.collect { | |
|condPat| | |
Pfunc({ | |
|e| | |
if (condPat[0].value(e)) { | |
condPat[1].value(e) | |
} { | |
nil | |
} | |
}) | |
}; | |
^this.new( | |
inputPattern, | |
*conditionPatterns | |
).init | |
} | |
init { | |
replace = true; | |
gate = false; | |
insert = false; | |
} | |
fixPattern { | |
|p| | |
^p.isFunction.if({ | |
Pfunc({ |e| e.use(p) }) | |
}, { | |
p | |
}) | |
} | |
asStream { | |
var stream; | |
var inputStream = inputPattern.asStream; | |
replacements.do { | |
|replacement| | |
inputStream = PwithStream( | |
parStream: PparStream(), | |
inputStream: inputStream, | |
replacementStream: this.fixPattern(replacement).asPattern.asStream, | |
); | |
} | |
// [conditions, patternFunctions].flop.flatten.pairsDo { | |
// |condition, patternFunction| | |
// | |
// inputStream = PwithStream( | |
// PparStream(), | |
// inputStream, | |
// condition, | |
// this.fixPattern(patternFunction), | |
// replace, | |
// gate, | |
// insert: false | |
// ); | |
// }; | |
^inputStream | |
} | |
} | |
+Ppar { | |
// *new { | |
// |list, repeats=1| | |
// ^Ppar2.new(list, repeats); | |
// } | |
} |
This file contains 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
( | |
Pdef(\input, Pbind( | |
\dur, 2, \legato, 1, | |
\octave, 3, \scale, Scale.kurd, | |
\degree, Pseq([0, 1, 7, 3, 5], inf) | |
)); | |
) | |
( | |
Pdef(\with, Pwith( | |
Pdef(\input), | |
// Each additional argument is a function that is called for every event | |
// If the function returns a non-nil value, it is treated as a pattern and replaces | |
// the input event, with some variation to what "replace" means. | |
{ | |
// randomly replace with a pattern 50% of the time | |
if ([true, false].choose) { | |
Pbind( | |
\dur, 1/4, | |
\octave, 4, | |
\degree, Pseq([-4, 3, 1, 2], inf) | |
) | |
} | |
} | |
).trace).play; | |
) | |
( | |
Pdef(\with, Pwith( | |
Pdef(\input), | |
{ | |
// If a function is provided, that function is evaluated once for each incoming event, | |
// with event keys filling in function arguments (same as recursive phrasing). | |
// Note the slightly weird function-within-a-function syntax (because the outer | |
// function is for deciding WHETHER to replace and e.g. returning nil). | |
{ | |
|dur, degree| | |
// Replace single events with a faster arpeggio | |
Pbind( | |
\dur, dur / 10, | |
\degree, degree + Pseq([-4, 3, 1, 2], inf) | |
) | |
} | |
} | |
).trace).play; | |
) | |
( | |
// Pdef(\with).clear; | |
Pdef(\with, Pwith( | |
// Replacement patterns follow the timing (\dur, \sustain) of the incoming note - | |
// for example, with a shorter sustain/legato, patterns will be clipped to a shorter | |
// length - with a longer legato, patterns will extend and overlap following the | |
// incoming events. | |
Pbind( | |
\legato, 2 | |
) <> Pdef(\input), | |
{{ | |
|dur, degree| | |
Pbind( | |
\dur, dur / 8, | |
\degree, degree + Pseq([-1, 4, -8], inf) | |
) | |
}} | |
).trace).play; | |
) | |
( | |
// Pdef(\with).clear; | |
Pdef(\with, Pwith( | |
// Replacement patterns follow the timing (\dur, \sustain) of the incoming note - | |
// for example, with a shorter sustain/legato, patterns will be clipped to a shorter | |
// length - with a longer legato, patterns will extend and overlap following the | |
// incoming events. | |
Pbind( | |
\legato, 2 | |
) <> Pdef(\input), | |
{{ | |
|dur, degree| | |
Pbind( | |
\dur, dur / 8, | |
\degree, degree + Pseq([-1, 4, -8], inf) | |
) | |
}} | |
).trace).play; | |
) | |
( | |
// Pdef(\with).clear; | |
Pdef(\with, Pwith( | |
Pbind( | |
\stepSize, 3 | |
) <> Pdef(\input), | |
// Outer event keys are visible in the context of your pattern via Pkey, | |
// which is an alternative to using function args to pick these up. | |
{ | |
Pbind( | |
\dur, Pkey(\dur) / 8, | |
\degree, Pkey(\degree) + (Pkey(\stepSize) * Pseq([-1, 2, -3], inf)) | |
) | |
} | |
).trace).play; | |
) | |
( | |
// Pdef(\with).clear; | |
Pdef(\with, Pwith( | |
// Outer events have several keys that control how pattern replacement happens.... | |
// All option keys, plus their default values. | |
Pbind( | |
// Replacement patterns should be gated to the \sustain duration of the incoming event. | |
// If `false`, replacement patterns can play as long as they want - be careful, if the patterns | |
// play forever then you'll just stack up infinitely-playing patterns indefinitely! | |
\gatePattern, true, | |
// If `true`, generate new patterns based on multichannel expansion of the input event. | |
// For example, Pbind(\flop, true, \degree, [1, 2, 3]) would produce THREE replacement patterns, | |
// one for each degree (by default, only one is generated and \degree will be visible as an array). | |
\flop, false, | |
// If `true`, replace the input event with the pattern. | |
// If `false`, play the original event AND the pattern together. | |
\replace, true, | |
// If `true` AND several input events occur at the same time (e.g. from a Ppar), then | |
// only the LAST event is replaced (e.g subsequent events at the same time replace old patterns) | |
// If `faise`, each input event generates a new pattern. | |
\replaceAll, false, | |
// If `false`, timing (dur/sustain) follows the input event, e.g. the overall timing | |
// of input events will not be changed. | |
// If `true`, replacement pattern will be "inserted", e.g. it will play until it's finished, | |
// before the next input event is processed. Note that this can occasionally have some | |
// unexpected results, which may be bugs or may simply be theoreticall correct-but-strange behavior.... | |
\insert, false, | |
// If `true`, events from the replacement pattern will replace ALL events for as long as the replacement | |
// event is running. The idea here is you can have a replacement like Pbind(\octave, Pkey(\octave).finDur(6) + 2), | |
// and it will raise the octave of all input events by 2 for 6 beats. Note that this ONLY really makes | |
// sense with (\gatePattern, false) | |
\filter, false, | |
) <> Pdef(\input), | |
{ | |
// note that these keys can also be set inside the replacement function like this, | |
// so you don't have to pollute your outer pattern with unrelated things. | |
~gatePattern = false; | |
Pbind( | |
\dur, Pkey(\dur) / 8, | |
\degree, Pkey(\degree) + (Pkey(\stepSize) * Pseq([-1, 2, -3], inf)) | |
) | |
} | |
).trace).play; | |
) | |
( | |
// limit pattern to 2 beats regardless of outer event | |
Pdef(\with, Pwith( | |
Pbind( | |
\dur, Prand([1, 1.5, 2], inf), | |
\octave, Pxrand([3, 4, 5], inf) | |
) <> Pdef(\input), | |
{ | |
~gatePattern = false; | |
Pbind( | |
\dur, Pconst(2, 1/6), | |
\degree, Pkey(\degree) + Pseq([-1, 2, -3], inf) | |
) | |
} | |
).trace).play; | |
) | |
( | |
// Don't replace, meaning play original events AND replacement pattern | |
Pdef(\with, Pwith( | |
Pdef(\input), | |
{ | |
~replace = false; | |
Pbind( | |
\octave, 5, | |
\amp, 0.03, | |
\dur, 1 / 6, | |
\degree, Pkey(\degree) + Pseq([-1, 2, -3], inf) | |
) | |
} | |
).trace).play; | |
) | |
( | |
// insert, meaning ignore the input events duration and play the entire replacement Pbind | |
// until its finished - here we randomly set the number of values pulled from the Pser to | |
// limit it. | |
Pdef(\with, Pwith( | |
Pdef(\input), | |
{ | |
~insert = true; | |
Pbind( | |
\octave, [4, 5].choose, | |
\dur, 1 / 12, | |
\degree, Pkey(\degree) + Pser([-1, 2, -3, 2, -1], rrand(4, 24)) | |
) | |
} | |
).trace).play; | |
) | |
( | |
// For 1/4 events (see our .coin condition), we will add a filter that modifies | |
// the octave of all incoming events according to `octBoost`, for `duration` beats. | |
// Note that we can potentially have multiple filters going at once - they are applied | |
// in the order they were created (in our case, multiple octave modifiers can be applied). | |
Pdef(\with, Pwith( | |
Pbind(\dur, 1/4) <> Pdef(\input), | |
{ | |
var octBoost, duration; | |
if (0.25.coin) { | |
~filter = true; ~gatePattern = false; | |
octBoost = [-1, 1, 2].choose; | |
duration = [3, 4, 6].choose; | |
"boosting octave by % for % beats".format(octBoost, duration).postln; | |
Pbind( | |
\octave, (Pkey(\octave) + octBoost).wrap(2, 7).trace, | |
\dummy, Pfindur(duration, 0) | |
) | |
} | |
} | |
)).play; | |
) | |
( | |
Pdef(\with, Pwith( | |
Pbind(\dur, 1/4) <> Pdef(\input), | |
{ | |
var octBoost, duration; | |
if (0.25.coin) { | |
~filter = true; ~gatePattern = false; | |
octBoost = [-1, 1, 2].choose; | |
duration = [3, 4, 6].choose; | |
"boosting octave by % for % beats".format(octBoost, duration).postln; | |
Pbind( | |
\octave, Pkey(\octave) + octBoost, | |
\dummy, Pfindur(duration, 0) | |
) | |
} | |
} | |
).trace).play; | |
) | |
( | |
// If more than one replacement is provided, theyre simply chained such that e.g.: | |
// Pwith(input, replacement1, replacement2) | |
// is equivalent to: | |
// Pwith(Pwith(input, replacement1), replacement2) | |
Pdef(\with, Pwith( | |
// 1 event every 4 beats, provide a list of degrees | |
Pbind( | |
\dur, Prand([1, 2, 4] / 1.5, inf), \legato, 1, | |
\scale, Scale.hexSus, | |
\chord, Pseq([ | |
[-1, 2, 4, 5], | |
[-2, 2, 4, 5] - 1, | |
], inf) | |
), | |
// replace with a arp-y pattern based on the chord | |
{{ | |
|chord| | |
Pbind( | |
\dur, 1 / 6, | |
\degree, Pseq(chord, inf), | |
\octave, 3, | |
) | |
}}, | |
// Occasionally replace our event with a slowed down ascending | |
// series of events. | |
{ | |
0.05.coin.if { | |
~gatePattern = false; | |
Pbind( | |
\dur, Pser([1 / 0.75], 6), | |
\amp, 0.1, | |
\octave, Pkey(\octave) + 1 + Pseries() | |
) | |
} | |
} | |
).trace).play; | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment