Watch the state of a Routine from outside

I want to trigger some actions, as soon as a routine has stopped playing. I know that thisThread.state or thisThread.isPlaying methods exist, but I don’t know how to use them to continuously listen to the routine from outside. Any hints and tips are appreciated.

You cant in the main thread, but you can in another Routine.

Writing this code on mobile untested so there will be errors.

c = CondVar();
r1 = Routine ({
   ...
   c.signalAll;
});
r2 = Routine({
   c.wait;
   ...
});

Alternatively you could alter the routine…

mk_r = { |then|
   Routine({
      ...
      then.();
   })
}
2 Likes

There’s no notification for changes to the state of a Thread - but this probably isn’t the right place to look for this. Threads themselves are a relatively low-level concept in SuperCollider - a notification when a Thread finishes execution would be a little like a notification when a Function finishes: a useful concept, but something you’d implement at a higher level.

Threads are more or less resumable functions - they end when there are no more statements to execute - so the simplest way to get a notification when a thread ends is just signal at the end:

(
~myThread = Routine({
    3.do {
        |i|
        i.postln;
        1.wait;
    };
    thisThread.changed(\done); // nothing left to do, so we signal!
});
~myThread.play;
~myThread.addDependant({ 
    |obj, signal|
    if (signal == \done) {
        "done!".postln
    }
});
)

It’s easy to make this generic - the embedInStream method will let one thread take over for another, so you can wrap any stream or thread in something like the above:

~myThread = Routine({ 3.do({ |i| i.postln; 1.wait } });
~notifier = Routine({
    ~myThread.embedInStream;
    thisThread.changed(\done); // nothing left to do, so we signal!
});

Or make a little factory function for this:

~threadNotifier = {
  |thread, doneFunc|
  Routine({
     thread.embedInStream;
     doneFunc.()
  })
};
~myThread = ~threadNotifier.(
    Routine({ ... }),
    {
        "I'm done now".postln;
    }
);
~myThread.play;

Of course, there are existing abstractions already for “do this one thing, and then do this other thing” - the above is roughly equivalent to:

~myThread = Pseq([
    Routine({ ... }),
    Routine({ "I'm done now".postln; })
]).asStream.play;

(Sorry if this is off-topic or things you already know about threads, hoping to spark some ideas!)


One other distinction to make here: Thread’s are in general not thing that play, they are just resumable functions (green threads / coroutines, in a technical sense) - isPlaying is in a way a bit of confusing terminology here. Generally, you’d have some kind of player that owns a Thread - this player would:

  1. Ask the thread for it’s next value via thread.next - by convention (for playable threads) this would be a number representing a wait time.
  2. Wait for value seconds.
  3. Wake up, and ask for the next value from the thread.
  4. Repeat until value == nil (importantly, it does NOT wait until thread.isPlaying = false, only until a nil is yielded).

There’s a bit of obfuscation here since when you do something like Routine({...}).play, there’s no object that is the “player” - this yield-and-wait functionality is only a few lines of code, and Clock’s can just handle it themselves. This is why “watching a thread’s state from the outside” is a bit tricky in this kind of basic case: normally there would be some player responsible for scheduling that thread, but in basic cases this isn’t really an object managing this.

But for more normal cases like playing Event’s, you’d have a player that does the above yield-and-wait stuff, but combines it with other functionality. For example, Event streams or Pbind’s use EventStreamPlayer, which knows how to schedule and play Events. EventStreamPlayer does send notifications for when it starts and stops playing (see cases of e.g. this.changed(\stopped); in Stream.sc). PauseStream is another player class that can play a Stream or Thread, and allow it to be started/stopped from the outside - it also sends playing and \stopped notifications.

Note that you can get the player for a particular thread by doing thread.threadPlayer - this may be something like an EventStreamPlayer, or in very basic cases it might be the thread itself.


So, the tldr:

  1. If you just want to perform an action when your Thread runs out of things to do (in a very generic way), just wrap that thread as I mentioned in the first examples. It’s worth remembering that there are many existing ways of scheduling one thing after another in SC, so it might be that what you want is better accomplishes by embedding one Routine inside another or using a sequence abstraction like Pseq.
  2. If you want to interact with something that’s doing musical or time-based playback from the outside, you should interact with the player for that thread and not the thread itself - most players send notifications like \playing, \stopped that are great for hooking up to UI, posting a notification, etc.
1 Like

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.