Issues with Pbindef.set()

Hi,

I noticed that setting a Pbindef value of a parameter through the .set() method works for numeric values but not for patterns.

a = Pbindef(\a, \instrument, \default);
a.play;
a.set(\degree, 1); // works
a.set(\degree, Pseq([0,2,4],inf)); // doesn't work

I also noticed that if a parameter is passed as argument to Pbindef, .set() cannot be used to change it.

a = Pbindef(\a, \instrument, \default, \degree, 0);
a.play;
a.set(\degree, 1); // doesn't work

Is that how it’s supposed to be? Is there a reason behind these?

Cheers.

It’s because Pdef in general chains its envir as an event before the source. So “by design”, because the envir is meant to be editable by gui with sliders, as long as it’s an actual Dictionary or sub-class thereof. (You can replace the whole envir with a Pbind for example, but that will lose such gui-editing features… and there’s little point in doing that since you chain Pdefs too.)

Think of the envir as the (gui-editable) data and the source of the Pdef as the program code that operates on that. This is a bit different “philosophy” than Pbind where everything (code and data) is “mixed together” and it’s up to you to separate it, if you want that. (You can’t gui a Pbind though.)

I just happened to post an example to another question showing the Pdef philosophy of what to put where.

As far internals go, effectively (although I’ve changed the var names a bit in the first method–there are more intermediates in the actual code.)

EventPatternProxy : TaskProxy {
	source_ { arg pattern;
        //...
		envir !? { pattern = pattern <> envir };
        //...
        source = pattern
    }

	envir_ { arg dict;
		envir = dict;
		this.source = source;
	}
}

And set is inherited way back from PatternProxy, doing the obvious thing:

PatternProxy : Pattern {
	set { arg ... args;
		if(envir.isNil) { this.envir = this.class.event };
		args.pairsDo { arg key, val; envir.put(key, val) };
		this.changed(\set, args);
	}
}

1 Like

Great explanation. Thanks a lot!

The pVarGui method produces a standard gui with the synthdef’s and/ or global metadata specs. So for a quick sequencing gui of the default instrument you can e.g. write:

\default.pVarGui.gui

Then you can add and replace key-value/stream pairs, so this is almost like aPbind.gui. It is explained in Chapter 2 of miSCellaneous_lib’s help file “VarGui shortcut builds”. However I suppose this is one of those cases where an intendedly “convenient” solution, doing a number of things under the hood, turns out to be of questionable practical value.
I have been working with Pbinds + VarGuis for many years and, on the contrary I hardly ever use pVarGui. I suppose the reason is that from case to case there are always very specific control demands and a generalised Pbind gui control couldn’t manage that, no matter how clever it would be …

Looking at this from a different point of view. Pbindef’s help file doesn’t explicitely mention the ‘set’ method - the standard way to assign values and / or a streams with a Pbindef is writing

Pbindef(\xy, \param, Pbla, ...)

Now why are you wanting to use ‘set’ instead ? My assumption, and please contradict me if I’m wrong, is that it is more intuitive and shorter than the standard syntax.
Conceptually I like Pbindef very much, but the syntax for replacements IMO is unintuitive (because building and setting are the same) and lengthy.
With the PLbindef wrapper class you can set like this:

PLbindef(\x, \dur, 0.2)
	
...
	
~x.dur = 1

// with EventShortcuts turned on:

~x.d = 1

Compounding the confusion: You do use set for Pbindef’s underlying model, PbindProxy – but not for Pbindef itself.

p = PbindProxy(\degree, Pwhite(0, 7, inf), \dur, 0.25);
q = p.play;

p.set(\degree, Pseq([
	Pseries({ rrand(-4, 5) }, 1, { rrand(4, 7) }),
	Pwhite(0, 7, { rrand(5, 10) })
], inf));

q.stop;

Pbindef(\x, \degree, Pwhite(0, 7, inf), \dur, 0.25).play;

// Pbindef(\x).set(...);  // wrong!
// right:
Pbindef(\x, \degree, Pseq([
	Pseries({ rrand(-4, 5) }, 1, { rrand(4, 7) }),
	Pwhite(0, 7, { rrand(5, 10) })
], inf));

Pbindef(\x).stop;

hjh

1 Like

Well, truth is… I was writing a class to do just what PLbindef does!
Didn’t know about PLbindef. Your assumption is absolutely right, I wanted to use a more intuitive syntax.

I couldn’t agree more.

I had found my way using Pbindef(\xy, \param, Pbla, ...) anyway, but I was now faced with dynamically creating methods like this. Awesome. Thanks for coding it!

I am curious about how PLbindef dynamically creates methods and maps them to Pbindef parameters. Looking at the code I’m not sure what part of it does the job. My guess is that it’s by setting the object to be used as prototype with environment.know in the *new method. Am I right?

May I also ask what does the L stand for in PLbindef? Supercollider class names are often a bit obscure to me : )

Awesome job, by the way. I love the miSCellanous library and use it a lot!

Ahh… yes PbindProxy overrides set so instead of setting in envir it sets in pairs, which is specific to PbindProxy.

But oddly Pbindef extends Pdef (not PbindProxy), and so Pbindef’s set does the same as Pdef’s, EventPatternProxy’s & PatternProxy’s (and not what PbindProxy.set does).

This confusion is probably necessary to some extent, since for Pbindef the pairs-setting (not envir-setting) calls look like constructor calls all the time; and those actually redirect to the PbindProxy.set

p = PbindProxy()
p.set(\foo, 12)
p.envir // -> nil
p.pairs // -> [ foo, a PatternProxy ]

// You can still use a PbindProxy.envir directly though
p.envir = (haha: 66)
p.iter.next // -> ( 'haha': 66, 'foo': 12 )

For contrast

Pbindef(\pah, \bar, 33)
Pbindef(\pah).envir // -> nil

Pbindef(\pah).set(\foo, 5)
Pbindef(\pah).envir // -> ( 'foo': 5 )

Pbindef(\pah).iter.next // -> ( 'bar': 33, 'foo': 5 )

// So where is \bar stored? Confusingly:
Pbindef(\pah).pairs // -> [  ]
Pbindef.findRespondingMethodFor(\pairs) // -> Object:pairs
// But
Pbindef(\pah).source // -> a PbindProxy
Pbindef(\pah).source.pairs // -> [ bar, a PatternProxy ]

N.B. I think few use PbindProxy directly, and the same goes for EvenPatternProxy and PatternProxy probably. The fairly long names were probably intended to be a discouragement from using them directly instead of their global dict wrappers (Pdef, Pbindef, Pdefn).

Also, I personally don’t use Pbindef/PbindProxy much either because of the confusing way in which it appends new kes to the end of pairs, but replaces keys wherever they are found. So it’s hard to mentally keep track of “program order” inside a Pbindef, which is needed if the value-patterns have any dependencies between each other.

1 Like

Happy to hear that it’s being used ! Myself I’m using PLbindef only from time to time, more for fun, but I like the replacement options especially of PLbindefPar, which extend the functionality to a bunch of parallel Pbindefs.

Yes, object prototyping is involved via a separate Environment, which is produced under the hood (a PLbindefEnvironment)

PLbindef / PLbindefPar were added later than the main suite of PLx patterns. There the PL could stand for “placeholder” (proxy) patterns (using dynamic scoping with envirs in contrast to JITLib), alternatively the L could stand for “lazy evaluation” (before PLx I often used Pn + Plazy constructs), I’m not sure what came to my mind first.

1 Like

Indeed. But for Pdef.envir, luckily it’s an exposed object so creating “shortcuts” for setting those is a easy as assigning the envir to some var etc.

d = Pdef(\pah).envir

d.dur = 0.2

And likewise for gui-ing stuff by Pdef name, e.g. I have a helper (much stripped down here):

// normally part of a bigger gui
~tb2 = TabbedView2.new().resize_(5);

// "gui envir" method
(~ge = { |pdefname, numlines=10|	var tabv = ~tb2.add(pdefname);
	ParamGui(Pdef(pdefname).envir, numlines, tabv, tabv.bounds.origin_(0@0)) })

~ge.(\pah)
// in fact I have a Symbol method extension so I just do
\pah.ge

(The fact that you have to recompile the classlib for something as trivial as the last line is quite annoying in my opinion! Maybe Symbol at least should have some kind of custom runtime-registration of “extensions” like Event does.)

Also, I sometime (ab)use the envir for (short!) functions. The event system auto-evaluates quite a few things as functions few so e.g. the following “just works”

Pdef(\meh, Pbind()).play.set(\dur, 0.2)

// then, e.g. (this is fully editable as cs in gui)
Pdef(\meh).set(\degree, { rrand(1,8) })

// also streams, but these don't gui well at all
Pdef(\meh).set(\degree, (1..8).iter.repeat)

// oh noes, there's stream-pattern code in my "pure data"
Pdef(\meh).set(\degree, Pseq((8..1), inf).iter)

Alas it’s not possible two switch from a number to a function in the present EnvirGui itself, but it works the other way around when you type a number in a cs-text field, albeit the spec gets weird sometimes. You need send an explicit signal to the right ParamView to change view type to cs-text if there’s simple number it it.

There are some gotchas when using this hack if values are used as inputs in other patterns, but surprisingly everything that gets sent as a synth arg as functions gets evaluated. It’s a bit obscure how that works but basically everything sent to the server as synth args gets asControlInput called on it and for the whole class tree of AbstractFunctions that does value e.g.:

{3}.asControlInput // -> 3

(degree: {rrand(1,8)}, dur: 0.2).play // hit a few times

The last line actually makes 'freq': a BinaryOpFunction because it plugs the degree function we gave into a formula, but eventually it gets resolved ok, at least in this case, when there are no arguments passed to the function. For args that get sent more directly to the server (i.e. synth controls with uncommon names) there are fewer gotchas.

But yeah, there’s an asymmetry here between Pdef.envir where you can at best have stream/routines and Pder.source which can have patterns. Although you can define some simple helper that turns patterns into streams upon insertion, I don’t quite see the point since you can also do

Pdef(\duh, Pbind()).play.set(\dur, 0.2)

Pdef(\before, Pbind(\degree, Pfunc { rrand(1,8) }))

Pdef(\duh, Pdef(\duh).source <> Pdef(\before))

It works because the chain is now

Pdef(\duh).source <> Pdef(\before).source <> Pdef(\duh).envir

Doing this will stop play though if \before is no bound to something.

And of course, I can spiffy Pdef(\before) so it reads its random interval endpoints from some keys, which can be set in its own envir. This is where having a tabbed gui of envirs helps…

Pdef(\before).set(\degLo, 4, \degHi, 16)

~ge.(\before)

Pdef(\before, Pbind(\degree, Pwhite(Pkey(\degLo), Pkey(\degHi))))

The chain is now

Pdef(\duh).source <> Pdef(\before).source <> Pdef(\before).envir <> Pdef(\duh).envir

Although it’s still a bit clunky compared to what an ideal “merged” interface would be. But one has to decide how much clutter wants into a single list of params… If want to move the new params (degLo & degHi) to the latter, e.g. they are important enough I don’t want them on a separate tab, I can do e.g. “move over”

Pdef(\duh).envir.putAll(Pdef(\before).envir)

To make the envir/sliders in \duh effective, those in \before’s envir need to be gone. Its “Pbind code” (Pdef.source) can read/receive them from Pdef(\duh).envir now.

Pdef(\before).envir.clear

If you just do Pdef(\before).envir = () that will work sound-wise, but the gui gets stuck with the old object not part of the sound chain anymore, which can definitely be confusing.

Side note: when you clear a Pdef (with Pdef(\bah).clear), its envir is not actually cleared; you need to do that separately.

I tried it and it doesn’t work for me.

a = Pdef(\pah).envir // returns -> nil

If you haven’t set something in it before, sure. Do e.g. Pdef(\pah).envir = () beforehand.

ok. Got it! Thanks : )

You can add this yourself. It would look something like:

+ Symbol {
    doesNotUnderstand { |selector ... args|
        ... your dispatch here...
    }
}

I would argue against a general-purpose Symbol:doesNotUnderstand that interprets the symbol as an index into Pdef, but the fact that nobody has done it for everyone in the main class library doesn’t forbid you from doing it in your environment.

hjh

I surely agree with that. I was thinking more like a predefined doesNotUnderstand that uses a map/dict to look up some “hooks” that make Symbol work more like an Environment with know true. Since unlike an Environment, a Symbol doesn’t have sub-components (like keys), it would need a registration function somewhat similar to what Event.addEventType does. From the user’s perspective something like, e.g.

Symbol.addPseudoMethod(\pdef, { |sym| Pdef(sym) } )
// and after that you could do (sans recompling classlib)
\boo.pdef // -> Pdef(\boo)

Of course that’s not a killer use case in terms of saving much typing, I just used it for illustration, but e.g. wslib defined Symbol.p which does PenvirGet, which is more useful in shorter expression in Pbinds etc. (And more officially, Symbol.kr and .ar for NamedControls.)

There’s already global storage that you can use any way you like: Library.

It’s true that a + ClassExtension {...} can’t add class or instance variables. But if you want a Symbol function registry, you can do it today as a private extension, or even as a quark if you wanted to publish it.

+ Symbol {
	*addPseudoMethod { |selector, function|
		Library.put(\symbolMethods, selector, function);
	}
	
	doesNotUnderstand { |selector ... args|
		var func = Library.at(\symbolMethods, selector);
		if(func.notNil) {
			^func.valueArray(args);
		} {
			DoesNotUnderstandError(this, selector, args).throw;
		}
	}
}

I still have a feeling this sort of thing should be optional (not part of the base class library – mainly because I’ve seen too many “wouldn’t it be nice if…?” new features go in without careful consideration of the drawbacks, where eventually somebody will find, and get annoyed by, inconsistencies in the object interfaces), but if it’s useful to you as an extension, there’s nothing stopping you.

hjh

1 Like

I’ve kinda solved this to my satisfaction using Pbindef via a wrapper class where setting a property removes it and re-adds it.

My uber class effectively returns this (very simplified):

Pchain(Pbindef(\set ...dynamic properties...) <> Pbind(...base properties...))

A set method on the class becomes:

set {arg ...args;

	forBy(0, args.size-1, 2, {arg i;

		var k = args[i];
		var v = args[i+1];

		Pbindef(\set, k, nil);
		Pbindef(\set, k, v);

		...etc...
	}

Using doesNotUnderstand syntactic sugar you can execute something like this sequentially with the expected results:

~a.degree = 0;
~a.foo = Pseq([0, 1, 2], inf);
~a.degree = Pkey(\foo);

However, if you continue to make changes it stops working - which is sucky.
But setting values in “batch” works…

~a.set(
	\foo, Pseq([0, 1, 2, 4], inf),
	\degree, Pkey(\foo)
);

or within a block

(
~a.foo = Pseq([0, 1, 2, 4], inf);
~a.degree = Pkey(\foo);
)

…since each property gets re-added in the correct order.

So you just have to know that when you have dependencies between properties you need to set them together

1 Like

I remember looking at PLbindef and liking that more explicit “duality”. With Pdef.envir, there’s the issue that sometimes you’d want the “*def-object” to be notified when certain changes happen in/via the envir. But you don’t get that if Pdef.envir is geared to accept any “legacy” Environment/Event. But there are some issues with subclassing Environments as I mentioned in that thread too.

I’ve noticed that when I set II degree, I can’t set the 1 degree block anymore:

// 1 degree
    (
    Pbindef(\pbass, [\degree, \dur], Pxrand([
    		Pseq([
    			[0, 1/4],
    			[4, 1/4],
       		 	[7, 1/2],	
    			[7, 1/2]
    		], 4),

	Pseq([
			[1, 1/4],
			[3, 1/4],
   		 	[\, 1/4],	
			Pn([8, 1/8], 2)
		], 4),
	], inf)); 
)

// II degree 
(
Pbindef(\pbass, \degree, 
	Place([0, [0, 3], [3, 5, 7, 12]], inf),
)
)

Is the empty symbol in the second pseq a typo?

Cheers,
eddi
https://soundcloud.com/all-n4tural/sets/spotlight
https://alln4tural.bandcamp.com