Watch the state of a Routine from outside

Concretely:

(
var taskWatcher;

t = Task {
	5.do { |i|
		i.postln;
		1.0.wait;
	};
}.play;

taskWatcher = SimpleController(t)
.put(\stopped, {
	taskWatcher.remove;
	"task ended".postln;
});
)

hjh

1 Like

I have updated my code to use a CondVar. I still am having a problem of, different threads (routines) run simultaneously. Here is my code, maybe you can see what am I doing wrong?
In the first block I simply define to routine factory functions, with my condvar in them waiting. The end of each routine should then trigger the next waiting routine. Then I define another routine (kind of a main routine for starting the piece), which can be filled with as many routine-factory functions as needed.

(
// ~no_running_wavedrop = true;
~condvar = CondVar();
~wave_routine_gen = {
	arg stream_count, drop_count, fund, freq_grow, init_dur, dur_grow;
	Routine({
		~condvar.wait;
		// ~no_running_wavedrop = false;
		~overtone_gen.reset;
		stream_count.do {
			~waves_routine.value(drop_count, ~overtone_gen.(fund), freq_grow, init_dur, dur_grow).play;
			~del.value.wait;
		};
		// ~no_running_wavedrop = true;
		~condvar.signalOne;
	})
};
~drop_routine_gen = {
	arg stream_count, drop_count, fund, freq_grow, init_dur, dur_grow,
	att, rel;
	Routine {
		~condvar.wait;
		// ~no_running_wavedrop = false;
		~overtone_gen.reset;
		stream_count.do {
			~drops_routine.value(drop_count, ~overtone_gen.(fund), freq_grow, init_dur, dur_grow, att, rel).play;
			~del.value.wait;
		};
		// ~no_running_wavedrop = true;
		~condvar.signalOne;
	}
};
)

(
Routine {
	~drop_routine_gen.(2, 10, 100, 1.2, 1, 0.8, 0.01, 0.1).play;
	~wave_routine_gen.(2, 10, 300, 1.34, 2, 0.7).play;
}.play
)

~condvar.signalOne

A code preview would be really appreciated!

This means I would need one Simplecontroller, for each Task/Routine?

Yes, one SimpleController listens to only one model.

hjh

And you would start playing next Tasks when say \stopped is signaled at the end of each Task, since my problem is to find a way to play multiple Tasks/Routines in sequence, one after another.

~c = CondVar();
~a = Routine({
	\startA.postln;
	5.wait;
	\doneA.postln;
	~c.signalOne
});
~b = Routine({
	~c.wait;
	\startB.postln;
	2.wait;
	\doneB.postln;
});
{~a.play; ~b.play}.()

This does what you have described.

If you already writing something like this then just make those both functions.

(
Routine {
	~drop_routine_gen.(2, 10, 100, 1.2, 1, 0.8, 0.01, 0.1).play;
	~wave_routine_gen.(2, 10, 300, 1.34, 2, 0.7).play;
}.play
)

What I want actually is to have two blueprints which I can use multiple times, for example something like:

~drop_routine_gen.(...);
~drop_routine_gen.(...);
~wave_routine_gen.(...);
~drop_routine_gen.(...);
etc.

That’s why putting a wait in front of one of them wouldn’t solve the problem, because the order in which they are to be appeared and executed is arbitrary.

  1. Something will have to wait, somewhere.

  2. So you need to decouple the waiting from the next-thing that you’re waiting for.

Basically that’s a producer-consumer problem: something as yet undefined produces the datum “what comes next,” and the thread (or thread sequence) consumes that datum and acts on it.

Probably a queue is the best data structure for this (first in, first out) and I’d probably implement that as a LinkedList. “Producing” means adding to the tail of the list; “consuming” means .popFirst from the head of the list.

var cond = CondVar.new;
~queue = LinkedList.new;

~something_gen = {
    Routine {
        ... do the stuff ...
        cond.signalOne;
    }.play;
};

~queue.add(\something_gen);
~queue.add(\other_gens);
...

~ctlThread = Routine {
    var nextsym;
    while {
        nextsym = ~queue.popFirst;
        nextsym.notNil
    } {
        nextsym.envirGet.value;
        cond.wait;
    }
}.play;

Haven’t tested this but I think it’s pretty close.

hjh

Still having a little trouble figuring out your intention here, but I do get the sense that the Condition signaling is just a more verbose and complicated way of doing something that there are better mechanisms for?

If your intention is to have a fixed sequence of re-usable Routines that play one after another, it’s relatively simple - the one piece that’s incorrect in the various code examples is that Routine is NOT the right structure. Routine is a Stream and Stream’s are generally (though not exclusively) one-time-use objects - when they’re done playing, they’re done. Something like Prout (the pattern wrapper around Routine) produces a new Routine each time asStream is called, or each time it’s embedded in another Stream. It’s your blueprint - so, given that:

(
~drop_routine = Prout({ 3.do { |i| "drop".postln; 1.wait; } });
~wave_routine = Prout({ 3.do { |i| "wave".postln; 1.wait; } });

~seq1 = Pseq([
    ~drop_routine,
    ~drop_routine,
    ~wave_routine,
    ~drop_routine
]);
~seq1.asStream.play;
)

Suppose you want to change the order or add new patterns from the outside… This is easy enough if you just replace the Pseq with your own sequencing routine, one that uses a list from “outside”:

(
~sequence = [~drop_routine, ~drop_routine, ~wave_routine];

~drop_routine = Prout({ 3.do { |i| "drop".postln; 1.wait; } });
~wave_routine = Prout({ 3.do { |i| "wave".postln; 1.wait; } });

~seq1 = Prout({
    inf.do {
        |index|
        ~sequence[index].embedInStream; // if this is nil (we're out of things) the player will abort
    }
});
~seq1.asStream.play;
)

~sequence = ~sequence.add(~wave_routine);
~sequence = ~sequence.add(~drop_routine)

And, suppose that you want some complex “orchestrator” logic that determines what sequence will play next? This is easy enough in the wrapping Prout as well - the logic here is simple, just an if branch, but you can probably imagine ways this could be more sophisticated.

(
~control = 0;

~drop_routine = Prout({ 3.do { |i| "drop".postln; 0.1.wait; } });
~wave_routine = Prout({ 3.do { |i| "wave".postln; 0.1.wait; } });

~seq1 = Prout({
    var nextRoutine = ~drop_routine;
    
    inf.do {
        nextRoutine.embedInStream;
        "routine is done, which is next?".postln;
        
        if (~control < 64) {
            nextRoutine = ~wave_routine
        } {
            nextRoutine = ~drop_routine
        }
    }
});
~seq1.asStream.play;
)

~control = 65;
~control = 5;

James is right, using a queue data structure is probably a good way to continuously fill up your orchestrator pattern with new things to play - but the structure of the pattern is roughly the same, just a different way of managing the list.

FWIW one subtle thing that probably isn’t working as you intend it here… Calling play here completely forks the Routine off to play on it’s own :). The outer Routine doesn’t wait, it just moves on, so the result is that you just play both routines at once. They immediately pause because of the Condition - if you release that Condition from the outside, they both resume immediately (probably not the intention?).

If you want a Routine to take over control in the context of another stream, you need to use embedInStream. So, an example of two streams playing one after another, with a Condition used (from the outside) to step to the next Routine looks more like:

(
Routine {
	~condvar.wait;
	~drop_routine_gen.(2, 10, 100, 1.2, 1, 0.8, 0.01, 0.1).embedInStream;
	~condvar.wait;
	~wave_routine_gen.(2, 10, 300, 1.34, 2, 0.7).embedInStream;
}.play
)

Is this a bug? I though that signalOne was supposed to only be caught once? because the following does exactly that.

~c = CondVar();
~mk_a = {|i|
	Routine({
		~c.wait;
		postf("start A : %\n", i);
		1.wait;
		postf("done A : %\n", i);
		~c.signalOne
	})
};
~mk_b = {|i|
	Routine({
		~c.wait;
		postf("start b : %\n", i);
		1.wait;
		postf("done B : %\n", i);
		~c.signalOne
	})
};
10.collect({|i| 
	0.5.coin.if({~mk_a.(i)}, {~mk_b.(i)}) 
}).collect(_.play);
~c.signalOne // start

Also, this plays them in order, which I was surprised by…

Ah my bad, you used signalOne which does only wake one. I was reading this with the semantics of Condition, which works equivalent to .signalAll.

So I’ve looked again at this… it works as described.

(
~condvar = CondVar();
~wave_routine_gen = {
	arg stream_count, drop_count, fund, freq_grow, init_dur, dur_grow;
	Routine({
		~condvar.wait;
		~overtone_gen.reset;
		stream_count.do {
			\waves.postln;
			1.wait;
		};
		~condvar.signalOne;
	})
};
~drop_routine_gen = {
	arg stream_count, drop_count, fund, freq_grow, init_dur, dur_grow,
	att, rel;
	Routine {
		~condvar.wait;
		~overtone_gen.reset;
		stream_count.do {
			\drop.postln;
			1.wait;
		};
		~condvar.signalOne;
	}
};
)

(
Routine {
	~drop_routine_gen.(2, 10, 100, 1.2, 1, 0.8, 0.01, 0.1).play;
	~wave_routine_gen.(2, 10, 300, 1.34, 2, 0.7).play;
}.play
)

~condvar.signalOne

returning…

drop
drop
waves
waves

The thing is, these always are evaluated in the order you define them in the bottom Routine, so you probably ought to use another method and make that static structure clear.

What do you mean by arbitrary? That word could apply to many different things here. Do you mean, you want them to be evaluated in a random order? not in the order they were added?

Since these always evaluate in order I think this might already be a queue of sorts under the hood.

In my case there still exist the problem, that next routines start playing although the last ones are not completely quit yet. I think it is better that I share my whole code, so you can test it yourself and hear the result. The result I want is in the last routine, to hear the next section as soon as the previous one has completely stopped sounding. Here is my repository:

You can evaluate the blocks top to bottom.

This can all be done with patterns and the composition of patterns much simpler than with routines.

Wait, do you mean .play or stopped making sound? i.e., after the sythns have been freed.

Sorry, I meant making sounds

If your intention is to have a fixed sequence of re-usable Routines that play one after another

This basically is what I want. I have tried a version with prout:

~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;
	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;
				};
			}.play;
			x.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
)

The problem I still have here, is that the second waves enters, before the last sound of the first waves disapears. What I exactly want, is to find a way to start the second waves, to come in after the first waves has completely stopped.

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