I have 5 forked functions within a routine - but calling “stop” on the routine doesn’t seem to stop things. I’m sure I’m missing something obvious to most, but maybe someone could advise me in the correct way to write this?
~r = Routine({
5.do{
fork{
inf.do{|o|
var m;
m = {SinOsc.ar(400.rand, 0, 0.01)}.play;
5.rand.wait;
m.release(5.0.rand);
}}};
});
~r.play;
~r.stop;
You’re creating Routines that continue running even when the parent Routine is stopped. That’s how it is.
You can’t access all child routines from the parent routine in SC. The language doesn’t do this for you; you must track the children to control them. You can do this in different ways. It can be just a List or a Dictionary.
(
f = {
var parentRoutine, children;
children = List[];
parentRoutine = Routine({
5.do { |i|
var child = Routine({
inf.do {
"child %".format(i).postln;
1.wait;
}
});
children.add(child);
child.play;
};
});
[parentRoutine, children]
};
)
# a, b = f.();
a.play;
b.do(_.stop);
Also, when you stop the routine(s) it doesn’t automatically release the active synths… so you probably want to keep track of them somehow and release them when you stop the routine
(
~synths = ();
~forked = 5.collect {
fork {
inf.do { |o|
var m;
m = {SinOsc.ar(400.rand, 0, 0.01)}.play;
~synths[m.nodeID] = m;
s.sync;
5.rand.wait;
m.release(5.0.rand);
~synths[m.nodeID] = nil;
}
}
};
)
~forked.do(_.stop); ~synths.do(_.release(5.0.rand));
Yeah, that’s another confusion. It comes because of the operating system’s threads, which behave just like that. SC Routine is a thread, BUT is very different from an OS thread in Linux or MacOS.
Anyway, the actual issue is that Server resources – such as Synths – always have to be managed manually; there is no garbage collection or reference counting.
Routines are Threads in sclang. Using the same name for things that behave in entirely different ways can sometimes create confusion. But it’s probably some brain condition I have.
Yes, the term “Threads” is confusing as sclang Threads/Routines are really coroutines. But note that “Thread” can have very different meanings in general (hardware thread, OS thread, green thread, etc.)
However, I don’t see the source of confusion. What is it about OS threads that users might confuse with sclang Routines?
It’s really not. There is no child-parent relationship between OS threads in a process. In other words: a thread does not own other threads. If you spawn thread B on thread A, B may still run after A has finished. You always need to keep track of threads and join them when they have finished. (Alternatively, you can also detach them, but that is generally discouraged.)
When a “parent” process dies/exits, all its threads are terminated. (I am not talking about threads that are kind of “peers” in the process)
I mean, I did not explain why I thought it could create confusion. But you keep trying to find something there. We can just talk about this (I would actually enjoy the conversation), I just don’t think that’s the right place.
When a process exits, all its resources are freed (threads, memory, file handles, etc.) This has nothing to do with OS threads in particular. I just thought that was an odd reference and needed some clarification. Now that we have clarified it I think we can leave it that
Anyway, the point I was trying to make in my first comment is this:
Although sclang is a garbage collected language, all Server resources – Synths, Groups, Buffers, Busses – have to be managed manually by the user. As we have seen, it is easy to accidentally leak Server resources. Unfortunately, there is no “defer” or “cleanup” mechanism in the language. Sclang doesn’t even have user-defined finalizers. (You can install a finalizer in C++, though.)
One quick question about this solution: ~forked seems to play instantly, without the need for a “play.” I could wrap ~forked in a function and play - but that changes how the rest of the code operates . Is there another step to having this as a more discretely controllable element?
Sure, wrap it in a function and evaluate it using .value or just .()
(
~playfunc = {
~synths = ();
~forked = 5.collect {
fork {
inf.do { |o|
var m;
m = {SinOsc.ar(400.rand, 0, 0.01)}.play;
~synths[m.nodeID] = m;
s.sync;
5.rand.wait;
m.release(5.0.rand);
~synths[m.nodeID] = nil;
}
}
};
};
)
~playfunc.();
~forked.do(_.stop); ~synths.do(_.release(5.0.rand));
(.value aka .() will basically evaluate the code block as though you used cmd-enter. .play is a convenience method that will wrap the function as a SynthDef and send it to the server.)
Here is a way to track the forked routines automatically (though admittedly obscure). This also fixes the non-released nodes by scheduling their release, rather than 'wait’ing for it. (“Waiting” for release is bad because, if you stop the routine, then you’ve also stopped the release from occurring.)