There’s another thread somewhere on this forum, where I had experimented with an object that would allow dependent calculations to be written in any order.
Anyway, copy-paste the class definitions at the bottom into a .sc
file in your extensions directory, and then – this is just taking your original Pbind and changing it to LazyPbind:
(
~sf = Buffer(s, 283821, 1, 1000); // spoof
~durPatternA = Pwhite(1, 4, inf) * 0.25;
p = LazyPbind(
\instrument, \sampler,
\sndbuf, ~sf,
\dur, Pser([
~durPatternA,
Pseq([2.pow(-4)], 16),
Pseq([0.25], 16),
], inf),
\rate, 2,
\pan, Pfunc {
|i|
if(i [\numFrames] >=(829086.00))
{rand2(1)}
{0}
},
\pos, Pser([
Pseg([~sf.numFrames*0.90, ~sf.numFrames*0.90], 4, repeats: 1),
Pseg([Pbeta(~sf.numFrames*0.0, ~sf.numFrames*0.5, 0.4, 0.6, 1), Pbeta(~sf.numFrames*0.5, ~sf.numFrames*1.0, 0.4, 0.6, 1)], 4, repeats: 1),
], inf),
\numFrames, Pkey(\pos).trace,
);
)
q = p.asStream;
q.next(());
-> ( 'instrument': sampler, 'numFrames': 255438.9, 'dur': 0.75, 'sndbuf': Buffer(1000, 283821, 1, nil, nil),
'pan': 0, 'rate': 2, 'pos': 255438.9 )
What this does is: 1. When \pan
tries to access i[\numFrames]
, it looks for \numFrames
and resolves it. 2. To resolve \numFrames
, it must resolve \pos
. 3. \pos
doesn’t refer to anything else in the event, so the pattern resolves normally and then it backtracks through the other keys to finish them.
It’s experimental – I don’t think it’s ready to pull into the class library. (Also, you might think, “Why isn’t this the default Pbind?” but it’s about three times slower, so it wouldn’t be appropriate for every use.)
a = Array.fill(50, { |i| [("param" ++ I).asSymbol, Pwhite(0, 99, inf)] }).flat;
(
p = Pbind(*a).asStream;
bench { 10000.do { p.next(Event.new) } };
)
time to run: 0.76619667600005 seconds.
(
p = LazyPbind(*a).asStream;
bench { 10000.do { p.next(Event.new) } };
)
time to run: 2.197149558 seconds.
hjh
// proof of concept - hjh 2020-09-11
// basically Thunk, possibly could fold this into Thunk
LazyResult : AbstractFunction {
var func, value;
var originalFunc;
var <>method;
*new { |func, method(\value)|
^super.newCopyArgs(func, nil, func, method)
}
value { |... args|
^if(this.isResolved) {
value
} {
value = func.performList(method, args);
func = nil;
value
}
}
isResolved { ^func.isNil }
reset { func = originalFunc; value = nil }
}
LambdaEnvir {
var envir;
var resolvingKeys;
*new { |env, method|
^super.new.init(env, method);
}
init { |env, method|
envir = Environment.new;
env.keysValuesDo { |key, value|
envir.put(key, LazyResult(value, method));
};
}
at { |key|
var entity = envir.at(key);
if(entity.isResolved) {
// should be an already-calculated value,
// but we need to return it below
entity = entity.value;
} {
if(resolvingKeys.isNil) {
resolvingKeys = IdentitySet.new;
};
if(resolvingKeys.includes(key)) {
Error("LambdaEnvir: Circular reference involving '%'".format(key)).throw;
};
resolvingKeys.add(key);
protect {
this.use {
entity = entity.value(this);
// "LambdaEnvir resolved '%' to %\n".postf(key, entity);
};
} {
resolvingKeys.remove(key);
};
};
^entity
}
put { |key, value| envir.put(key, value) }
use { |func|
var saveEnvir;
var result;
if(currentEnvironment === this) {
^func.value
} {
protect {
saveEnvir = currentEnvironment;
currentEnvironment = this;
result = func.value;
} {
currentEnvironment = saveEnvir;
};
^result
};
}
keysValuesDo { |func|
envir.keysValuesDo(func)
}
reset {
this.keysValuesDo { |key, value| value.reset }
}
parent { ^envir.parent }
proto { ^envir.proto }
parent_ { |parent| envir.parent_(parent) }
proto_ { |proto| envir.proto_(proto) }
}
LazyPbind : Pbind {
embedInStream { |inevent|
var proto = Event.new;
var lambdaEnvir;
var event;
patternpairs.pairsDo { |key, value|
proto.put(key, value.asStream)
};
loop {
if(inevent.isNil) { ^nil.yield };
lambdaEnvir = LambdaEnvir(proto, method: \next)
.parent_(inevent.parent).proto_(inevent.proto);
event = inevent.copy;
lambdaEnvir.keysValuesDo { |key|
var value = lambdaEnvir[key];
if(value.isNil) { ^inevent };
if(key.isSequenceableCollection) {
if(key.size > value.size) {
("the pattern is not providing enough values to assign to the key set:" + key).warn;
^inevent
};
key.do { |key1, i|
event.put(key1, value[i]);
};
} {
event.put(key, value);
};
};
inevent = event.yield;
}
}
}
+ Object {
isResolved { ^true }
}