Race condition in mapping a node control in Pproto?

I’m trying to encapsulate the (working) code I have here in a Proto. But I’m having a race condition on startup on trying to map the “perma sig gen” node to a bus (giving a FAILURE IN SERVER /n_mapn Node XXXX not found. Can anyone suggest how to work around for this? (And I mean something that doesn’t break the encapsulation, i.e. abandon Pproto.)

(SynthDef(\beep, {
	var out = \out.ir(0), amp = \amp.kr(0.5), freq = \freq.kr(440);
	ReplaceOut.ar(out, amp * SinOsc.ar(freq).dup);
}).add;)


(SynthDef(\menv, {
	var out =  \out.ir(88), atk = \atk.kr(0.15), rls = \rls.kr(0.5), gate = \gate.kr(1), slvl = \slvl.kr(0.5);
	var insig = In.kr(out), env = EnvGen.kr(Env.asr(atk, slvl, rls, 0), gate, doneAction:2);
	var eSlopesDown = (HPZ1.kr(env) < 0), outsig = eSlopesDown.if(env, max(insig, env));
	ReplaceOut.kr(out, outsig);
}).add;)



(p = Pproto({ var onres;
	~ampBusNum = ((type: \controlBus, channels: 1).yield)[\out];
	~ampBusNum.postln;
	onres = (type: \on, instrument: \beep, amp: 0.1).yield; // non-zero amp so I can hear it get created
	onres.postln;
	Node.basicNew(s, onres[\id][0]).map(\amp, ~ampBusNum); // onres[\server] no diff
	~egroup = (type: \group).yield; // not sure this works either without indexing id
	~egroup.postln;
}, Pbind(
	\instrument, \menv,	\addAction, 1,
    \group, Pkey(\egroup), \out, Pkey(\ampBusNum),
	\dur, Pseq([0.2], inf),
	\legato, Pseq([0.5, 0.7, 0.9, 0.975, 1, 1.1, 1.151])
)).trace.play);

I get something like (snipping the irrelevant output):

( 'channels': 1, 'type': controlBus )
1
( 'instrument': beep, 'amp': 0.1, 'type': on )
( 'instrument': beep, 'msgFunc': a Function, 'amp': 0.1, 'server': localhost, 
  'isPlaying': true, 'freq': 261.6255653006, 'hasGate': false, 'type': on, 'id': [ 1661 ] )
( 'type': group )
( 'type': group, 'id': 1662, 'server': localhost )
( 'instrument': menv, 'group': ( 'type': group, 'id': 1662, 'server': localhost ), 'out': 1, 'dur': 0.2, 
  'legato': 0.5, 'addToCleanup': [ a Function ], 'delta': 0.2, 'egroup': ( 'type': group, 'id': 1662, 'server': localhost ), 'addAction': 1, 
  'ampBusNum': 1 )
FAILURE IN SERVER /n_mapn Node 1661 not found

Looking through Event.sc, there seems to an event of type \map defined too, which might sequence everything correctly without me having to instantiate the Node.basicNew, but I’m not sure how to use the \map events. Actually, scratch that, map seems to be intended to be called directly as a “NodeEvent”, not actually scheduled. Those NodeEvents don’t work terribly well, by the way

It’s definitely a race and not something else because if I do

(p = Pproto({ var onres;
	a = ~ampBusNum = ((type: \controlBus, channels: 1).yield)[\out];
	~ampBusNum.postln;
	onres = (type: \on, instrument: \beep, amp: 0.9, freq: 660).yield; // try amp: ~ampBusNum directly
	onres.postln;
	n = Node.basicNew(s, onres[\id][0]).map(\amp, ~ampBusNum); // onres[\server]
	//~rawsig.map(\amp, ~ampbus);
	~egroup = (type: \group).yield;
	~egroup.postln;
}, Pbind(
	\instrument, \menv,	\addAction, 1, \group, Pkey(\egroup), \out, Pkey(\ampBusNum),
	\dur, Pseq([4.2], inf),
	\legato, Pseq([0.5, 0.7, 0.9, 0.975, 1, 1.1, 1.151])
)).trace.play);

And then hit it with n.map(\amp, a); after it starts playing it works as expected (i.e. envelopes begin to go in and out. I suppose the race happens because events go on a quant but Node.basicNew.map goes off immediately. I’m gonna try to schedule that as an event somehow.

Putting that map in a callback doesn’t make any difference alas:

(p = Pproto({ var onres, doTheMap;
	a = ~ampBusNum = ((type: \controlBus, channels: 1).yield)[\out];
	~ampBusNum.postln;
	doTheMap = { |ev| n = Node.basicNew(s, ev[\id][0]).map(\amp, ~ampBusNum).postln };
	onres = (type: \on, instrument: \beep, amp: 0.2, freq: 660, callback: doTheMap).yield;
	onres.postln;
	n = Node.basicNew(s, onres[\id][0]).map(\amp, ~ampBusNum); // onres[\server]
	//~rawsig.map(\amp, ~ampbus);
	~egroup = (type: \group).yield;
	~egroup.postln;
}, Pbind(
	\instrument, \menv,	\addAction, 1, \group, Pkey(\egroup), \out, Pkey(\ampBusNum),
	\dur, Pseq([4.2], inf),
	\legato, Pseq([0.5, 0.7, 0.9, 0.975, 1, 1.1, 1.151])
)).trace.play);

Except I get to fails in a row:

FAILURE IN SERVER /n_mapn Node 1773 not found
FAILURE IN SERVER /n_mapn Node 1773 not found

I’ve discovered the somewhat obscure feature (of the server protocol) that you can atomically initialize synth controls to bus numbers upon creation by using a string as the control value starting with the letter “c” or “a” followed by the (control or audio) bus number. Using this simplifies the code and fixes the intial map failure, but the 1st [m]env still doesn’t quite work properly; at least the following ones work ok now though.

(p = Pproto({ var onres;
	a = ~ampBusNum = ((type: \controlBus, channels: 1).yield)[\out];
	("c" ++ ~ampBusNum).postln;
	onres = (type: \on, instrument: \beep, amp: ("c" ++ ~ampBusNum), freq: 660).yield;
	onres.postln;
	~egroup = (type: \group).yield;
	~egroup.postln;
}, Pbind(
	\instrument, \menv,	\addAction, 1, \group, Pkey(\egroup), \out, Pkey(\ampBusNum),
	\dur, Pseq([4.2], inf),
	\legato, Pseq([0.5, 0.7, 0.9, 0.975, 1, 1.1, 1.151])
)).trace.play); 

Also need to set the bus value for the 1st env/event to work properly, since buses are not re-initialized on the server when reallocated (reallocation happens entirely on the client). I’m still getting a premature cutout at the end for now, though (and Pproto warns that it doesn’t know how to clean event type \bus which sets rather than allocated as a bus. Frankly, the bus allocation event(s) like \controlBus could probably lookup the \array value to initialize the bus too)

(p = Pproto({ var onres;
	a = ~ampBusNum = ((type: \controlBus, channels: 1).yield)[\out];
	("c" ++ ~ampBusNum).postln;
	(type: \bus, out: ~ampBusNum, array: [0]).yield.postln; // set bus value
	onres = (type: \on, instrument: \beep, amp: "c" ++ ~ampBusNum, freq: 660).yield;
	onres.postln;
	~egroup = (type: \group).yield;
	~egroup.postln;
}, Pbind(
	\instrument, \menv,	\addAction, 1, \group, Pkey(\egroup), \out, Pkey(\ampBusNum),
	\dur, Pseq([0.2], inf),
	\legato, Pseq([0.5, 0.7, 0.9, 0.975, 1, 1.1, 1.151])
)).trace.play); 

Oddly the premature cut-out only happens in 3.11.0 but not in 3.10.4… I’m not sure why…

My opinion remains that resource allocation is not best done within the pattern framework. It’s best handled in a wrapper object around a pattern, providing hooks for:

  • Object-level constructor and destructor;
  • Play-time constructor and stop-time destructor.

Yes, we have some patterns that create and destroy resources (and yes, those do have the advantage of being arbitrarily composed of all, at least in theory – this is probably why you’re drawn to them).

But I found they simply don’t work all that well – as you are, now, finding that they don’t work all that well.

Again, just my opinion, but – I think the reason why they don’t work so well is because pattern objects themselves shouldn’t be doing that job. Division of labor – let patterns do what they do best (data processing). Let something else that is designed for resource allocation handle the resource allocation.

Then many of the problems you’re now facing simply go away.

hjh

Yes I’m keenly aware of the bugs (esp. on cleanup), but if you need to fire hundreds of those “constructs” per minute, I think using the Event framework, which has some native (C++) optimizations is probably the best bet. I could be wrong though. Soon-ish I’ll have to decide whether I want to

  • fix the cleanup system
  • roll my own Even-like framework (as you did for coarser alloc-style events)
  • write a C++ UGen that does “attack-slope-preserving re-triggered envelopes” since re-triggering EnvGen doesn’t work as I need it for my application
  • write more generic “recursive UDO” UGen, that sends s_new to the server “in loopback”

I see… There’s a distinction, though, between patterns or events with resource allocation and cleanup. Pattern : event is 1 : many.

My framework isn’t about events – it’s a pattern wrapper, and I had meant these as relatively permanent objects. But if you’re firing off a hundred allocate-and-cleanup patterns per minute, then they must be temporary, meaning that their cleanups are temporary also.

I did, once upon a time, write an event prototype that allocated a bus for a control or audio signal, and deallocated it after the main synth was gone. That was to support the idea of a main synth, and some other signal that I could swap out per event. It worked fine, but I haven’t used it in a long time.

I have to admit that, after dozens of threads here, I have not much of a clear idea of your end goal. Clearly you have something in mind, but the questions tend to focus almost entirely on individual trees. So I feel like at times my suggestions might not always be relevant, because I have only an inkling that there’s a forest around, but not much idea what it looks like. I wonder if you wouldn’t mind giving a high-level overview. It might put the issues (arbitrary composability, automated allocation/cleanup, Routine stuff, server-side s_new… the field keeps getting wider and wider) into context. What are the constructs that you need hundreds of?

hjh

Actually the “premature cutout” is easy to avoid on both versions of SC. It’s simply a matter of making the dur >= sustain + rls in the last ‘menv’ envelope event in the Proto, i.e. one before the cleanup(s) kick in. (That happened not to be the case in the examples I used here.) It’s also a bit easier, conceptually, to work explicitly with the sustain duration than with legato in this approach.