Created
March 28, 2020 21:31
-
-
Save scztt/1887df908be4a282ebe4c187fdde9ad1 to your computer and use it in GitHub Desktop.
Pxfade
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
+Scale { | |
octDegreeToMidi { | |
|octave, degree| | |
var steps = this.tryPerform(\stepsPerOctave) ?? 12; | |
var note = degree.degreeToKey(this, steps); | |
var midinote = (octave * steps) + note; | |
^midinote; | |
} | |
midiToOctDegree { | |
|midinote| | |
var steps = this.tryPerform(\stepsPerOctave) ?? 12; | |
var root = midinote.trunc(steps); | |
var key = midinote % steps; | |
var degrees = (steps.neg + this.degrees) ++ this.degrees ++ (steps + this.degrees); | |
var tuningArray = (steps.neg + tuning.tuning) ++ tuning.tuning ++ (steps + tuning.tuning); | |
var octave = root / steps; | |
var tuningIndex, degree; | |
tuningIndex = tuningArray.indexInBetween(key); | |
tuningIndex = (tuningIndex - tuning.tuning.size) % tuning.tuning.size; | |
degree = degrees.indexInBetween(tuningIndex); | |
degree = (degree - this.size) % this.size; | |
^(octave: octave, degree: degree) | |
} | |
} | |
+SequenceableCollection { | |
octDegreeToMidi { | |
|octave, degree| | |
var steps = this.tryPerform(\stepsPerOctave) ?? 12; | |
var note = degree.degreeToKey(this, steps); | |
var midinote = (octave * steps) + note; | |
^midinote; | |
} | |
midiToOctDegree { | |
|midinote| | |
var steps = this.tryPerform(\stepsPerOctave) ?? 12; | |
var root = midinote.trunc(steps); | |
var key = midinote % steps; | |
var degree = this.indexInBetween(key); | |
var octave = root / steps; | |
degree = (degree - this.size) % this.size; | |
^(octave: octave, degree: degree) | |
} | |
} | |
+SimpleNumber { | |
degreeToKey { |scale, stepsPerOctave = 12| | |
var scaleDegree = this.round.asInteger; | |
var accidental = (this - scaleDegree); | |
^scale.performDegreeToKey(scaleDegree, stepsPerOctave, accidental) | |
} | |
} | |
+Scale { | |
performDegreeToKey { | scaleDegree, stepsPerOctave, accidental = 0 | | |
if (accidental != 0) { | |
if (accidental.isNegative) { | |
scaleDegree = scaleDegree - 1; | |
accidental = 1.0 - accidental.abs; | |
}; | |
^this.performDegreeToKey(scaleDegree, stepsPerOctave).blend( | |
this.performDegreeToKey(scaleDegree + 1, stepsPerOctave), | |
accidental | |
) | |
} { | |
stepsPerOctave = stepsPerOctave ? tuning.stepsPerOctave; | |
^(stepsPerOctave * (scaleDegree div: this.size)) + this.blendAt(scaleDegree, \wrapAt); | |
} | |
} | |
} |
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
Pdefn(\a, 100); | |
Pdefn(\b, 200); | |
Pdefn(\c, 300); | |
( | |
Pdefn(\xfadeTest, | |
Pxfade( | |
[ | |
Pdefn(\a), | |
Pdefn(\b), | |
Pdefn(\c), | |
], | |
Pseries(0, 0.1, 20), | |
fadeFunc: \random // Random weighted distribution between adjacent pairs | |
) | |
).asStream.nextN(20) | |
) | |
// Pxfade(inputs, fade, fadeFunc=\random ...args) | |
// inputs: a list of patterns or values to xfade between | |
// fade: a fade value, which indexes into inputs. E.g. if fade=2.5, it represents | |
// a 50% blend between the values at inputs[2] and inputs[3] | |
// fadeFunc: a function, name of function, or pattern producing names or functons, used to blend the values. | |
// args: additional parameters to pass to fadeFunc | |
// The fadeFunc argument accepts several symbols representing different | |
// strategies for blending values. In all cases, blends are done between | |
// adjacent pairs when more than 2 are specified in the initial list argument. | |
// Arguments to Pxfade AFTER fadeFunc are passed as arguments to the fadeFunc | |
// (though not all of them have arguments). | |
// | |
// \default: The default fadeFunc - this is initially set to \random but can be changed | |
// using: Pxfade.fadeFuncs[\default] = \blend | |
// \random: Random weighted distribution between adjacent pairs. | |
// \blend: Blending between adjacent pairs via a.blend(b, xfade). Blend | |
// falls back to \default if values are not blendable. | |
// \randBlend: Weighten random blend between adjacent pairs. | |
// \weightHash: When fading between adjacent pairs, a switches to b at a random but | |
// deterministic point based on the value of a and b separately. This means | |
// a=10 will behave somewhat consistantly when blended with other values. | |
// \combinedHash When fading between pairs, a switches to b at a random but | |
// deterministic pont based on the *value of both a and b together*. There's | |
// a unique crossover point for every unique combination of a and b. | |
// \balancedSum This blend chooses either a or b, such that the running average value | |
// converges on runningAverage(a).blend(runningAverage(b), xfade) over time. | |
// Basically, you get a mix of a and b that should add up to a value mid-point | |
// between them. This is mathematically incorrect, and only kind-of working. :) | |
// \flipFlop (args: flipPropability) | |
// When xfading between a and b, repeat one of the two, and randomly switch which | |
// one you're repeating according to the extra "flipPropability" parameter. E.g. a propability of 0.21 | |
// Pxfade([1, 2], Pseries(0, 0.01, 100), \flipFlop, 0.21).asStream.nextN(100) | |
// \octDegree (args: scale=(Scale.chromatic), fadeFunc=\blend ...args) | |
// Blend pairs of [degree, octave] by converting them to midi according to scale, then applying the | |
// fadeFunc argument, then converting back to [degree, octave] form. | |
// E.g. Pxfade([ [0, 3], [0, 4] ], Pseries(0, 0.1, 10), \octDegree, Scale.chromatic, \randBlend).asStream.nextN(10) | |
// \event (args: itemFadeFunc=\random ... additionalArgs) | |
// A special fadeFunc for blending event streams. Each value in the Event will be blended with itemFadeFunc. | |
// \degree and \octave are always blended using \octDegree, with the same itemFadeFunc. additionalArgs are passed | |
// to whatever fadeFunc is specified. See below.... | |
// | |
// fadeFunc can also be a function - or a pattern that produces functions - of the form: | |
// { |a, b, xfade ... additionalArgs| } | |
// where xfade is normalized 0..1, and additional args are the extra args after fadeFunc. | |
( | |
Pdef(\a, Pbind( | |
\octave, 3, \degree, 0, | |
\amp, 0.5 | |
)); | |
Pdef(\b, Pbind( | |
\octave, 4, \degree, 0, | |
\amp, 1 | |
)); | |
Pdef(\c, Pbind( | |
\octave, 5, \degree, Pseq([3, 5], inf), | |
\legato, 2, | |
\amp, 0.1 | |
)); | |
Pdefn(\xfadeEventTest, | |
Pbind(\dur, 1/6) | |
<> Pxfade( | |
[ | |
Pdef(\a), | |
Pdef(\b), | |
Pdef(\c), | |
], | |
Pseg([0, 2], [20]), | |
\event, | |
\randBlend | |
).trace | |
).play; | |
) |
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
Pxfade : Pattern { | |
classvar <fadeFuncs; | |
var <>inputs, <>fade, <>fadeFunc, <>fadeFuncArgs; | |
*initClass { | |
fadeFuncs = ( | |
event: Prout({ | |
var keys = IdentitySet(); | |
var blendStreams = (); | |
var ignores=[\octave, \degree], midinoteAssigned=false, scale, octDegree; | |
var resolvedFadeFunc, merged; | |
loop { | |
yield({ | |
|a, b, xfade, itemFadeFunc=\random ...args| | |
if (itemFadeFunc.isSymbol) { | |
itemFadeFunc = fadeFuncs[itemFadeFunc] ?? { | |
Error("No fadeFunc with specified name %".format(itemFadeFunc)).throw; | |
}; | |
}; | |
if (resolvedFadeFunc != itemFadeFunc) { | |
resolvedFadeFunc = itemFadeFunc; | |
blendStreams.clear(); | |
}; | |
[a, b].do { | |
|event| | |
if (event[\midinote].isNumber.not) { | |
event[\midinote] = event[\scale].octDegreeToMidi( | |
event[\octave] ?? 4, | |
event[\degree] ?? 0 | |
) | |
} { midinoteAssigned = midinoteAssigned || true }; | |
}; | |
merged = a.merge(b, { | |
|aVal, bVal, key| | |
var stream, value; | |
if (ignores.includes(key).not) { | |
stream = blendStreams[key] ?? { | |
blendStreams[key] = resolvedFadeFunc.asStream; | |
blendStreams[key]; | |
}; | |
value = stream.next().value(aVal, bVal, xfade, *args); | |
// "% / %: a(%) => b(%) = %".format(key, xfade, aVal, bVal, value).postln; | |
value; | |
} | |
}).parent_(a.parent).proto_(a.proto); | |
if (midinoteAssigned.not) { | |
merged.putAll( | |
(merged[\scale] ?? { Scale.chromatic }).midiToOctDegree(merged[\midinote]) | |
); | |
merged[\midinote] = nil; | |
}; | |
merged; | |
}) | |
} | |
}), | |
octDegree: { | |
|a, b, xfade, scale=(Scale.chromatic), fadeFunc=\blend ...args| | |
var degreeA, octA, degreeB, octB, midinoteA, midinoteB, value; | |
#degreeA, octA = a; | |
#degreeB, octB = b; | |
midinoteA = scale.octDegreeToMidi(octA, degreeA); | |
midinoteB = scale.octDegreeToMidi(octB, degreeB); | |
if (fadeFunc.isSymbol) { | |
fadeFunc = fadeFuncs[fadeFunc] ?? { | |
Error("No fadeFunc with specified name %".format(fadeFunc)).throw; | |
}; | |
}; | |
value = fadeFunc.asStream.next().value(midinoteA, midinoteB, xfade, *args); | |
value = scale.midiToOctDegree(value); | |
[value.octave, value.degree]; | |
}, | |
random: { | |
|a, b, xfade| | |
if (xfade > rrand(0.0, 1.0)) { | |
b | |
} { | |
a | |
} | |
}, | |
blend: { | |
|a, b, xfade| | |
if (a.respondsTo('+')) { | |
a.blend(b, xfade); | |
} { | |
fadeFuncs[\default].(a, b, xfade); | |
} | |
}, | |
randBlend: { | |
|a, b, xfade| | |
var f = { |r| log((r - 1).pow(2) / r.pow(2)) }; | |
if (a.respondsTo('+')) { | |
a.blend(b, xfade.lincurve(0.0, 1.0, 0.0, 1.0, f.value(1.0.rand))); | |
} { | |
fadeFuncs[\default].(a, b, xfade); | |
} | |
}, | |
weightHash: { | |
|a, b, xfade| | |
var weights, choice, f; | |
f = { |r| log((r - 1).pow(2) / r.pow(2)) }; | |
weights = [ | |
a.hash.linlin(-2147483647.0, 2147483647.0, 0.0, 1.0), | |
b.hash.linlin(-2147483647.0, 2147483647.0, 0.0, 1.0) | |
]; | |
weights = [ | |
(1 - xfade).lincurve(0.0, 1.0, 0.0, 1.0, f.(weights[0])), | |
(xfade).lincurve(0.0, 1.0, 0.0, 1.0, f.(weights[1])) | |
]; | |
weights = weights.normalizeSum; | |
choice = weights[0] < weights[1]; | |
if (choice) { | |
b | |
} { | |
a | |
} | |
}, | |
combinedHash: { | |
|a, b, xfade| | |
var hash; | |
hash = [a, b].hash.linlin(-2147483647.0, 2147483647.0, 0.0, 1.0); | |
if (xfade > hash) { | |
b | |
} { | |
a | |
} | |
}, | |
balancedSum: Prout({ | |
var sumA = 0, sumB = 0, sumActual = 0; | |
loop { | |
yield({ | |
|a, b, fade, leak=0.05| | |
var distA, distB, target, value, expBlend; | |
if (a.respondsTo('+').not) { | |
fadeFuncs[\default].(a, b, fade); | |
} { | |
// expBlend = { | |
// |a, b, blend| | |
// a.pow(1 + ((b.log / a.log) - 1 * blend)); | |
// }; | |
sumA = sumA * (1 - leak); | |
sumB = sumB * (1 - leak); | |
sumActual = sumActual * (1 - leak); | |
sumA = sumA + a; | |
sumB = sumB + b; | |
// target = expBlend.(sumA, sumB, fade); | |
target = sumA.blend(sumB, fade); | |
distA = abs((sumActual + a) - target); | |
distB = abs((sumActual + b) - target); | |
value = (distA < distB).if(a, b); | |
sumActual = sumActual + value; | |
value; | |
} | |
}) | |
} | |
}), | |
flipFlop: Prout({ | |
var index, rand; | |
loop { | |
yield({ | |
|a, b, fade, flipProp=0.1| | |
if (index.isNil || (1.0.rand < flipProp)) { | |
index = (1.0.rand > fade).if(0, 1); | |
}; | |
[a, b][index]; | |
}) | |
} | |
}), | |
); | |
fadeFuncs[\default] = fadeFuncs[\random]; | |
} | |
*new { | |
|inputs, fade, fadeFunc=\random ...args| | |
^super.newCopyArgs(inputs, fade, fadeFunc, args); | |
} | |
embedInStream { | |
|inval| | |
var blendStream; | |
var fadeFuncStream; | |
fadeFuncStream = fadeFunc; | |
if (fadeFuncStream.isSymbol) { | |
fadeFuncStream = fadeFuncs[fadeFuncStream] ?? { | |
Error("No fadeFunc with specified name %".format(fadeFuncStream)).throw; | |
}; | |
}; | |
fadeFuncStream = Pfunc({ | |
|func| | |
{ | |
|...args| | |
var argInputs, fade, fadeFuncArgs, fadeStart, subFade, subInputs; | |
argInputs = args[0..(inputs.size - 1)]; | |
fade = args[inputs.size]; | |
fadeFuncArgs = args[(inputs.size+1)..]; | |
subFade = fade.mod(1.0); | |
fadeStart = (fade - subFade).round(1).asInteger; | |
subInputs = [ | |
argInputs.wrapAt(fadeStart), | |
argInputs.wrapAt(fadeStart+1) | |
]; | |
func.value(subInputs[0], subInputs[1], subFade, *fadeFuncArgs); | |
} | |
}) <> fadeFuncStream; | |
blendStream = Pnaryop(\value, fadeFuncStream, inputs ++ [fade] ++ fadeFuncArgs).asStream; | |
while { | |
(inval = blendStream.next(inval)).notNil | |
} { | |
inval = inval.yield; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment