Preserving groups id or reference after CmdPeriod

This is how I define my Synth groups in my default workset:

(
ServerTree.removeServer(s);
ServerTree.add({
	~soundGrp=Group.new;
	~effectGrp=Group.after(~soundGrp);
	~mixerGrp=Group.after(~effectGrp);
	~masterGrp=Group.after(~mixerGrp);
	~mix=Synth(\Mixer,args: [\vol1,1,\vol2,1],target: ~mixerGrp);
},s);
CmdPeriod.run;
)

And one basic pattern:

p=Pbind(\scale, Scale.dorian, \degree, Pwhite(0,7), \dur, 0.25, \out, ~channel1, \group, ~soundGrp);

When pressing Ctrl+. all the groups are released and then recreated, and the variables are getting a new value. That’s ok, except that any pattern playing in one of those groups is “lost” after Ctrl+. . A p.play will not produced any sound, because the group referred in the pattern does not exist any more.

One option to get those patterns working again is to re-evaluate all the patterns after a Ctrl+. . But this is not really convenient to my opinion.

Are there better options ?

  • Is there a way to reuse groups id ? So that ~soundGrp remains unchanged after the Ctrl+. ?

  • What about references ? It is working fine for the patterns but not so well with Synths:

E.g.

// define as Ref objects
~soundGrp=`0;

// add default groups and synths
ServerTree.removeServer(s);
ServerTree.add({
	~soundGrp.value=Group.new;
},s);
)
// ~mix=Synth(\Mixer,args: [\vol1,1,\vol2,0.2],target: ~mixerGrp); // KO. 
~mix=Synth(\Mixer,args: [\vol1,1,\vol2,0.2],target: ~mixerGrp.value); // OK -> for Synth, the reference **must** be resolved. Prone to error.
p=Pbind(\scale, Scale.dorian, \degree, Pwhite(0,7), \dur, 0.25, \out, ~channel1, \group, ~soundGrp); // OK -> for patterns, the reference **must not** be resolved.

So, what would be a convenient method to “preserve” the groups variables after Ctrl+. , working fine with both Synth and Patterns ?

you can create (and later refer to) groups with the basicNew method specifying a nodeID… then call s.sendBundle(nil, yourGroup.newMsg(s,\addtoHead) etc to instantiate on server- see the help for Group.basicNew and newMsg for examples

This is an interesting question.

Briefly (and it took me some time to come around to this, but I finally agree with it) – if you’re using node abstractions (Synth, Group) along with the server’s node ID allocator, then the idea is to refer to the abstractions and allow other objects to get the ID from the abstraction on demand, rather than linking to specific IDs in advance. So it should not be necessary to preserve IDs across ctrl-dot (and if you find it is necessary with certain objects, then those objects might be using the node abstractions incorrectly).

The ~mix Synth KO example does suggest that perhaps the node abstraction interfaces should resolve Ref when it’s used as a target. (But, I’m not 100% convinced because, after ctrl-dot, you need to re-create the Synth anyway – and at the moment of recreation, ~soundGrp.value will resolve to the newly-created group. I don’t see the problem here.)

A pattern is an interesting case, where it’s entirely plausible to create the pattern as a behavioral template, and only play it later – but, as you found, streams resolve the Ref for you and there is no problem. (Or you could use \group, Pfunc { ~sndGroup }.)

So I’m not sure what remains that is unsolved.

hjh

I like this approach. Actually, specifying the nodeID is not even necessary, because they are “preserved” by the fact that basicNew is done only once.

// -- Groups
// init the groups as Ref object
~soundGrp=Group.basicNew(s);
~effectGrp=Group.basicNew(s);
~mixerGrp=Group.basicNew(s);
~masterGrp=Group.basicNew(s);


// CmdPeriod actions
ServerTree.removeServer(s);
ServerTree.add({
	~soundGrp.postln;
	~effectGrp.postln;
	~mixerGrp.postln;
	~masterGrp.postln;
	
	s.sendBundle(nil, ~soundGrp.newMsg(nil,\addToHead));	
	s.sendBundle(nil, ~effectGrp.newMsg(~soundGrp,\addAfter));	
	s.sendBundle(nil, ~mixerGrp.newMsg(~effectGrp,\addAfter));	
	s.sendBundle(nil, ~masterGrp.newMsg(~mixerGrp,\addAfter));	
	
	~mix=Synth(\Mixer,args: [\vol1,1,\vol2,0.2],target: ~mixerGrp);
	
	"CmdPeriod reset done".postln;
	
	
},s);
CmdPeriod.run;

I end up with 2 types of notations:

  1. With the byRef approach :
  • for the Synth, one have to resolve the reference with a \target: ~mygroup.value,
  • while for the Pattern, one can simply write \group: ~mygroup
  1. With the byVal approach :
  • for the Synth one can simply write \target: ~mygroup
  • but for the Pattern, a Pfunc must be used\group: Pfunc{~mygroup}

That’s something that I try to avoid in generally: multiple notations for the same effect. This is prone to error.

That would be great. Because that would allow to have a common approach for both Synth and Patterns.

Let me pull some other people onto this thread – @muellmusik , @VIRTUALDOG – legit question being raised here.

Our more or less official position about persistent group references has been that objects should keep a reference to a group, not to a specific ID – then cmd-dot can allocate a new ID for groups, and this would propagate down to any patterns or other types of players using the group object.

But, as noted above, typical usage of ServerTree is to create new objects for infrastructure nodes:

(
ServerTree.add({
	~srcGroup = Group.new;
}, Server.default);
)

But this means that any downstream object that refers to ~srcGroup can’t rely on a persistent object reference (so, every downstream object should be deleted and recreated on cmd-dot… not sure that’s what we want).

Currently, AFAICS, if you want a persistent reference, it has to be something like this:

(
ServerTree.add({
	var id;
	if(~srcGroup.isNil) {
		~srcGroup = Group.basicNew(s, -1);
	};
	~srcGroup.nodeID = s.nodeAllocator.alloc;
	s.sendBundle(nil, ~srcGroup.newMsg);
}, Server.default);
)

… which… c’mon, we’re not seriously going to expect users to come up with this formulation on their own.

lgvr123 came up with a clever solution: to set ~srcGroup to a Ref(), and then ServerTree assigns a new group into the Ref. But this is inconsistently supported – \group, aRefToAGroup in a Pbind-style pattern will dereference and return the group (OK), but Synth/Group targets don’t dereference (error).

I tend to agree that this is a rough edge in the interface – we say a persistent Node reference is the way to go, but it isn’t straightforward to do that.

I’m not sure which is better, though – to support Refs more thoroughly, or to create an easier way to create, and update IDs for, persistent Node objects. I tend to think the second is better.

Thoughts…?

hjh

Hi All!

So just to be clear, the objection to working with things like node IDs and server abstraction objects is an OOP one, i.e. it breaks encapsulation. ids should be an implementation detail, and one that we can change without breaking the interface. That aside, the point that the situation remains a bit clunky is well made!

Some years ago, @julian and I knocked together a proof of concept for persistent node objects. Basically you could tell an object to rebuild, and it would take care of getting a new id, etc. Any objects which held that node object then could treat it as live. IIRC, it took advantage of the add actions and targets to allow you to reconstruct whole trees of dependant nodes. (This is important as you may need to rebuild targets before you can rebuild the current node.) It worked I think and was quite slick and appealingly simple as far as it went. Solve an encapsulation problem by using it!

So why did it never make it in? Hard to recall, but I think one issue was the question of how much state to track. Order we obviously want to track, but just creation order, or current? Similarly with synths, if we want to recreate them we need to know any starting control values. Is that enough, or do we move to some more ambitious version of state synchronisation between language and server? (i.e. can of worms) At the time I think there was some concern about keeping things as light-weight as possible, so that may have been an objection.

I would prefer a solution along these lines. I think it’s less invasive and more nimble than the Ref approach, FWIW, though that’s a good idea! I’d suggest storing initial state only, since that’s in keeping with what I think is the desired usage. I can try and find the code for this, though it wouldn’t be hard to reconstruct. @julian may have other comments.

S.