How to detect when nodes, generated by Pbind, are done playing?

Hello everyone,

there is probably a trivial solution to this, but I cannot figure it out.

(
SynthDef(\MainRecordPlayer, { arg bufnum = 0;
    var signal;
	signal = SoundIn.ar()!2;
	RecordBuf.ar(signal, bufnum,recLevel: 1,preLevel: 0,loop: 0);
}).add;
);

~b1 = Buffer.alloc(s, s.sampleRate * 4, 2,bufnum: 1);

~recorder1 = Synth(\MainRecordPlayer,[\out, 0, \bufnum, ~b1]);
	~recorder1.free;



(
a = SynthDef(\playback_buf, { arg out, bufnum, amp, rate = 1, startpos = 0;
    var signal;
	signal = PlayBuf.ar(2,bufnum,rate,1,startpos,0,2) * amp;
	DetectSilence.ar(signal,0.1, 1,2);
    Out.ar(out, signal);
}).add
);

x = Synth(\playback_buf,[\bufnum, ~b1, \amp, 1]);

~list1 = [[0,0,0,1],[0,0,0,1],[0,0,0,1],[0,0,0,1]]


(
		~routine = Routine({
			var count = 0, spawner;
		g = Group.new;
	NodeWatcher.register(g);
			while ({(count) != ~list1_3.size},{
		count.postln;
			spawner = Spawner({| sp |
		sp.seq(Ppar([
				Pbind(\type, \note,\dur, 1,\instrument, \playback_buf, \bufnum, ~b1,\amp,Pseq(~list1[count]),\group, g)
		]));
	sp.suspendAll;
					}).play;
				0.1.wait;
				while({	spawner.isPlaying == true},{0.1.wait;});
					count = count + 1;
			});
	"end".postln;
}).play
	);

I would like the last while loop

while({	spawner.isPlaying == true},{0.1.wait;});

to delay the routine until all the nodes generated by the Pbind are freed.

At first thought I could just register the spawner or pbind with NodeWatcher, but realized that does not really help.

So I thought I could put them all into a group and do something like
group.size != 0 or group.isEmpty != true but that does not work either.

Then after some searching I bumped into this thread : Evaluate function when synth freed?

and tried to do use the \callback key mentioned there along with \finish

~group =  []

(
var myfun = { |n| (~group = ~group.add(n.nodeID))};
var myfun2 = { |n| (~group = ~group.remove(n.nodeID))};
p = Pbind(
	//\degree, Pwhite(0, 7, inf),
	\dur, 1,
	\amp, Pseq(~list1_3[1]),
	\finish,  { |ev| ev[\id] do: { |id| Node.basicNew(s, id).onFree(myfun); } },
	\callback, { |ev| ev[\id] do: { |id| Node.basicNew(s, id).onFree(myfun2); } }
).play;
)

while(group.size != 0 ).....

but it seems like I cannot use the \finish key the same way as callback

Is there any easy work around to this that I am not seeing ?

Last thing I came up with would be to somehow measure the bus output and once it drops to zero send a signal, or evaluate myfun function inside of the SynthDef instead. But those solutions somehow seem a bit out of place and overcomplicated ?

Thank you for any thoughts

finish is before creating nodes, so you don’t have access to the node ID here.

callback is just after the node is created, so it does have access to the ID, but this isn’t when the node finishes, so it’s the wrong time to remove the ID from the array.

The callback should add the ID and register a responder to catch the node’s end. (The OSCFunc argTemplate ensures this responder will fire only for the specific ID.)

\callback, { |ev|
    ev[\id].do { |id|
        ~group = ~group.add(id);
        OSCFunc({ ~group.remove(id) }, '/n_end', argTemplate: [id]).oneShot;
    };
}

hjh

Also, let me throw in a better way to wait for the nodes to stop: Condition.

The purpose of Condition is to remember which routines have been paused under its control (cond.wait or cond.hang), and wake them up again either on demand (cond.unhang) or when a Boolean condition becomes true. (So, we’re assuming that the condition is false at the moment when the thread should pause, and it will become true later.)

The condition here is that the array of IDs is empty: Condition({ nodeIDs.size == 0 }).

At the place where the routine should stop, I used cond.hang. (cond.wait is a little different – it checks the condition and pauses only if it’s false. But nodeIDs initial size is 0, so the condition is true initially – so it’s necessary to use the “force-pause” method hang.)

cond.signal means: check the condition and if true, let the routines continue. So it’s not necessary to rewrite the condition.

Also I made a couple of minor corrections, and allowed the IDE to reindent your code. SC doesn’t care about whitespace, but when you have more than one or two syntactic levels, consistent indentation makes it much much much easier for humans to read.

(
~routine = Routine({
	var count = 0, spawner;
	var nodeIDs;
	var cond = Condition({ nodeIDs.size == 0 });
	g = Group.new;
	// standard 'for'-style condition is "<" not !=
	// if count == 10, you shouldn't continue
	while { count < ~list1.size } {
		count.postln;
		spawner = Spawner({ |sp|
			sp.seq(Ppar([
				Pbind(
					\type, \note,
					\dur, 1,
					\instrument, \playback_buf,
					\bufnum, ~b1,
					\amp, Pseq(~list1[count]),
					\group, g,
					\callback, { |ev|
						ev[\id].do { |id|
							nodeIDs = nodeIDs.add(id);
							OSCFunc({
								nodeIDs.remove(id);
								// unblock the routine when condition is met
								cond.signal;
							}, '/n_end', argTemplate: [id]).oneShot;
						};
					}
				)
			]));
			// not sure this is really necessary
			// if you're here (after 'seq') then the Ppar already stopped
			sp.suspendAll;
		}).play;
		cond.hang;
		count = count + 1;
	};
	"end".postln;
}).play
)

hjh

2 Likes

Ahhh I see. Thank you ! I will need to read more into the OSCFunc.

And thank you for this additional explanation aswell, I really appreciate it! I use the while/wait loop everywhere. I thought there was a more efficient way and was aware of the Condition. But I somehow did not understand, at the time while I was looking into it, from where to call the condition.signal. Now it seems obvious. I even just discovered there is a solution for a single synth … .waitForFree; I suppose this approach is computationally less heavy ?

Haha, this feature is also new to me ! I will make sure to use it from now on.

Yeah, an explanation like @jamshark70’s should end up in the HelpFile IMO! Also, we miss an example where test is a Function: the HelpFile says it has to be a boolean, and it doesn’t make clear enough IMO that anything that responds to .value() with a boolean will work!

1 Like