Order of execution

I’m using it too :wink: 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

1 Like