I’m using it too It’s quite fun to be able to write e.g.:
SynthDef(\fmfb, { |out, gate = 1, freq = 440, detun = 1.008, freqlag = 0.08,
ratio = 1, index = 1.433, fbAmt = 0,
ffreq = 12000, rq = 1, ffreqMul = 1, fegAtk = 0.01, fegDcy = 0.1,
acc = 0,
atk = 0.2, dcy = 0.4, sus = 0.8, rel = 0.4,
mAtk = 0.2, mDcy = 0.4, mSus = 1, mRel = 1.2,
pan = 0, width = 0.6, amp = 0.1|
var n = 5;
var sig = LambdaEnvir((
freqs: { Lag.kr(freq, freqlag) * ~detunes },
detunes: { Array.fill(n, { detun ** Rand(-1, 1) }) },
mods: {
var sig;
sig = SinOsc.ar(~freqs * Lag.kr(ratio, freqlag), ~feedback % 2pi) * ~modEg;
LocalOut.ar(sig);
sig
},
feedback: { LocalIn.ar(n) * fbAmt },
cars: { SinOsc.ar(~freqs, (~mods * index) % 2pi) },
modEg: { EnvGen.kr(Env.adsr(mAtk, mDcy, mSus, mRel), gate) },
carEg: { EnvGen.kr(Env.adsr(atk, dcy, sus, rel), gate, doneAction: 2) },
mix: { Splay.ar(~cars, width, center: pan.clip2(1 - abs(width))) },
filter: { BLowPass.ar(~mix, (ffreq * ~feg).clip(20, 20000), rq) },
feg: { EnvGen.kr(Env([1, ffreqMul * ~accent, 1], [fegAtk, fegDcy], \exp)) },
accent: { acc + 1 },
out: { ~filter * (~carEg * amp) }
)).at(\out);
Out.ar(out, sig);
}).add;
Here’s a bit hacky fix for the {}.next
vs {}.value
issue:
// 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 }
}
hjh