>= ... etc not working in Pfunc "If" statement

Hi,

I am trying to get a conditional statement working (\pan key) but I’m curiously not able to - this normally isn’t a problem. I can check for equivalence == but anything else throws an error. At first, I referenced the key \pos, and when I got the initial error I thought I’d try an “clean it up” by referencing the \numFrames key but to no luck. Is it a problem with me trying to accomplish this with Pseg? Any insight welcome, thank you

(
Pbind(
	\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).post)
		{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,
).play;
)

http://doc.sccode.org/Tutorials/A-Practical-Guide/PG_06g_Data_Sharing.html#Reading%20values%20from%20the%20current%20event

“Be aware that Pkey can only look backward to keys stated earlier in the Pbind definition. Pbind processes the keys in the order given. In the example, it would not work to put legato first and have it refer to degree coming later, because the degree value is not available yet.”

It’s the same principle for event[\key in a Pfunc – in fact, Pkey was introduced as a shorter alternative to Pfunc { |event| event[\key] }.

You can’t do numeric operations on a number that hasn’t been assigned yet.

hjh

1 Like

Hi, thanks for the reply - I’m smacking my head, I’ve rectified this problem myself in the past but it just flew by me this time. Appreciated!

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 }
}
1 Like

Thank you, this has given me a lot to think about! It’s a very helpful solution to ensure dependent calculations are always executed. The speed is something to consider…but I’m wondering how this could be used in a compositional context…it feels very applicable to a “live” setting. Swapping out parameters…or calculating values at a later date. Could be very useful when branching perhaps?

Could you point me to the thread?

Thanks again

mjm

Hmm, maybe I’m misreading the problem here… but if you wrap values up as functions, they are only evaluated when the Event is played, at which point all of the keys are already available.

Pbind(
  \pan, { if (~numFrames.value >= 829086) { rand2(1) } { 0 } },
  \numFrames, { ~pos.value },
  // \pos, .... // this should be fine as it is....
)

Two other bits:

  1. If your key is a Synth argument and the value is a function, that function is always executed - so you can use functions in your Pbind and it will work transparently. For non-Synth-argument keys… functions will only be unwrapped if they are referred to elsewhere as e.g. ~something.value. Most interval event keys do this, but a few do not - I think ~tempo is one that doesn’t, which always trips me up. You should be fine with the code you’re using, but something to keep in the back of your head if you get weird errors later on…
  2. You can do James’ “evaluate-once” thunk behavior by doing e.g.:
    \numFrames, { ~numFrames = ~pos.value }
    to simply replace the function with the calculated value in the Event. This is how it’s done in many other places in the Event system. To be honest, this is almost for sure not worth it. There are performance impacts of course, but I regularly use Pbinds that are orders of magnitude more complex than what you’re describing, and I’ve never had Pbind-related performance problems.

Here:

I believe the note about performance problems was referring specifically to my benchmark of Pbind vs LazyPbind above. In the other thread, someone had asked “Is there any downside to making LazyPbind the default Pbind?” and I would say that slowing down every Pbind by a factor of 3 would not be a good thing to do casually. There aren’t any other claims about performance being made, as far as I can see.

hjh