Skip to content

Instantly share code, notes, and snippets.

@scztt
Created March 28, 2020 21:31
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 scztt/1887df908be4a282ebe4c187fdde9ad1 to your computer and use it in GitHub Desktop.
Save scztt/1887df908be4a282ebe4c187fdde9ad1 to your computer and use it in GitHub Desktop.
Pxfade
+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);
}
}
}
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;
)
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