Watch the state of a Routine from outside

Your inner Prout’s look correct in terms of the wait times. But, your outer Prout isn’t waiting for that Prout to finish, it’s only waiting x. I’m assuming from the code that the intention is to have the inner Prout sequences overlap, but then only proceed on to the next thing once they are fully finished? I think I better understand why you were thinking in terms of wait conditions for a routine now!

You could try Condition (which is what you were thinking of anyway I think):

~waves = {
	arg cnt, size, freq, freq_grow, initdel, del_grow;
	var dels = Array.geom(size, initdel, del_grow);
	var freqs = Array.geom(size, freq, freq_grow);
	var seq;
	var finishedCount = 0, finished = Condition({ finishedCount >= cnt });
	Prout {
		Array.fill(cnt, {0.1.rrand(0.7)}).do {
			arg x;
			Prout {
				dels.do {
					arg del, i;
					Synth(\wave,
						[\amp, 0.1, \dur, del, \fr, freqs.at(i)]
					);
					del.wait;
				};
				finishedCount = finishedCount + 1; 
				finished.signal();
			}.play;
			x.wait;
		};
		finished.wait();
	}
};

// and then
(
Pseq([
	~waves.(10, 20, 1000, 0.9, 0.1, 1.1),
	~waves.(10, 20, 7000, 0.8, 0.1, 1.2)
]).asStream.play
)

This is probably the most straightforward but also flexible / resilient way I can think of to do this? This should work even if your inner routines are radically different lengths, if you change the count, if the routines end before you hit the condition.wait also.

If you’re interested, Deferred is a bit better abstraction for synchronization across threads (it solves some similar problems of CondVar).

Specifically, it has a .using method that runs a function (which can have waits in it) and then resolves itself when that function returns. You can wait directly on the Deferred itself, which will wait until the .using function finishes, regardless of what thread it’s on.

(
var dels = Array.geom(10, 0.2, 0.95);
Prout({
    Array.fill(8, {0.1.rrand(0.7)}).collect({
        |x, i|
        var d = Deferred();
        fork {
            d.using({
                dels.do {
                    |d|
                    "Routine % waiting %".format(i, d).postln;
                    d.wait;
                }
            });
        };
        "waiting % before spawning a new one...".format(x).postln;
        x.wait;
        d
    }).do(_.wait)
}).asStream.play
)

(this might be helpful: deferred-examples.scd · GitHub)

There is – in CondVar:

  • wait adds the current thread player to the end of an array (prSleepThread).
  • signalOne pops the first thread player out of the array (this.prWakeThread(waitingThreads.removeAt(0));).

It turns out that, for array sizes up to somewhere between 1000 and 10,000, the array.removeAt(0) which CondVar uses is faster than a LinkedList – I think because LinkedList’s garbage collection load is heavier.

hjh