In order to include my suggestion in the help documentation, the following part would have to be changed, but it would be a breaking change:
}.play(SystemClock)
Would that be OK?
In order to include my suggestion in the help documentation, the following part would have to be changed, but it would be a breaking change:
}.play(SystemClock)
Would that be OK?
Hm, to be honest, I donât see any benefit in doing this (but it would cause concrete problems, by breaking user code that has GUI operations in a waitForBoot function â I think my live-show setup sequence does this! So please donât break my live-show setup ).
The key words are âtime-sensitive.â If you are trying to sync GUI work with time-sensitive operations (such as musical sequencing), then Iâd recommend the approach of running the time-sensitive control flow on TempoClock / SystemClock, and deferring only the bits that have to be deferred.
But the server boot process is not time-sensitive. There is no compelling reason to run it in a real-time thread.
hjh
Sure!
However, codes like this
(
//s.doWhenBooted {
s.waitForBoot {
Window.closeAll;
fork {
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
1.wait;
{ w.close }.defer
}
}
}
)
could be written as follows:
(
//s.doWhenBooted {
s.waitForBoot {
{ Window.closeAll }.defer;
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
1.wait;
{ w.close }.defer
}
}
)
My perspective could be from my lazy programming habitâŚ
Such a construction is unnecessary if one uses Routine
or Task
outside of s.waitForBoot
like this
(
//s.doWhenBooted {
s.waitForBoot {
Window.closeAll
};
fork {
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
// Until the local server is booted, the following warning will be displayed:
// WARNING: server 'localhost' not running.
1.wait;
{ w.close }.defer
}
}
)
or if one uses Pbind like this:
(
//s.doWhenBooted {
s.waitForBoot {
Window.closeAll;
~clockSound = Pbind(
\instrument, \default,
\midinote, Pseq([93, 81!3].flat, inf),
\amp, 0.05,
\dur, 1,
\legato, 0.7,
\callback, {
Routine {
q = Window().background_(Color.rand).front;
0.99.wait;
q.close.postln
}.play(AppClock)
}
).play
}
)
The advantage would be a somewhat simplified code structure for beginners and some of intermediate users. I do not think it is unimportant.
For advanced users who use SuperCollider in vscode for example, modifying these parts is not difficult. Finding and replacing text is a tedious process, but regular expression find and replace in a folder and its subfolders makes the process easier.
I would like to point out the method .plot
. In most cases, users do not need to care about plotter
. I think that defer{ ... }
and AppClock
could be in a similar relationship to .plot
and Plotter
if we change the clock of ServerStatusWatcher:doWhenBooted from AppClock
to SystemClock
.
There is an unknown amount of legacy user code that depends on waitForBoot running in AppClock â unknown meaning, potentially large.
Thereâs not a bug here, just confusion stemming from incomplete documentation. The fact that itâs not thoroughly documented is IMO not sufficient grounds to break existing user code.
With apologies, but I am 100% against this suggestion let me rephrase⌠while I appreciate the thought thatâs gone into it, breaking changes really need careful consideration* and IMO this one doesnât make the cut.
*Recalling Pamela Zâs ICMC '23 keynote, where she admitted she hasnât upgraded past Max 5 because her live-show patch is broken in later versions. Thatâs the hidden (or not-so-hidden) cost of breaking changes.
hjh
Yes, this may not be appropriate in SuperCollider 3.x.x.
But could it be considered for SuperCollider 4?
[EDITED]
Or we can give the fourth argument clock
and set its default value to AppClock
.
// Server
.waitForBoot(onComplete, limit: 100, onFailure)
.doWhenBooted(onComplete, limit: 100, onFailure)
// ServerStatusWatcher
.doWhenBooted(onComplete, limit: 100, onFailure)
Then, it will not be a breaking change!
I wonder if there might be a way that GUI calls could be automatically redirected rather than erroring when they are scheduled in a Thread?
(sorry for reposting due to typos)
I am not sure if I understand your question, but I donât think there are such a way without modifying the method definition in Server.sc
and ServerStatus.sc
.
My test is successful:
waitForBoot
and doWhenBooted
in Server.sc
waitForBoot { |onComplete, limit = 100, onFailure, clock = \AppClock|
// onFailure.true: why is this necessary?
// this.boot also calls doWhenBooted.
// doWhenBooted prints the normal boot failure message.
// if the server fails to boot, the failure error gets posted TWICE.
// So, we suppress one of them.
if(this.serverRunning.not) { this.boot(onFailure: true) };
this.doWhenBooted(onComplete, limit, onFailure, clock);
}
doWhenBooted { |onComplete, limit=100, onFailure, clock = \AppClock|
statusWatcher.doWhenBooted(onComplete, limit, onFailure, clock)
}
1.2. doWhenBooted
in ServerStatus.sc
doWhenBooted { |onComplete, limit = 100, onFailure, clock = \AppClock|
var mBootNotifyFirst = bootNotifyFirst, postError = true;
bootNotifyFirst = false;
^Routine {
while {
server.serverRunning.not
/*
// this is not yet implemented.
or: { serverBooting and: mBootNotifyFirst.not }
and: { (limit = limit - 1) > 0 }
and: { server.applicationRunning.not }
*/
} {
0.2.wait;
};
if(server.serverRunning.not, {
if(onFailure.notNil) {
postError = (onFailure.value(server) == false);
};
if(postError) {
"Server '%' on failed to start. You may need to kill all servers".format(server.name).error;
};
serverBooting = false;
server.changed(\serverRunning);
}, {
// make sure the server process finishes all pending tasks from Server.tree before running onComplete
server.sync;
onComplete.value;
});
}.play(clock.asClass)
}
AppClock
:(
//s.doWhenBooted {
s.waitForBoot {
Window.closeAll;
fork {
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
1.wait;
{ w.close }.defer
}
}
}
)
or(
//s.doWhenBooted({
s.waitForBoot({
Window.closeAll;
fork {
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
1.wait;
{ w.close }.defer
}
}
})
)
2.2. with SystemClock
(
//s.doWhenBooted({
s.waitForBoot({
{ Window.closeAll }.defer;
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
1.wait;
{ w.close }.defer
}
}, clock: SystemClock)
)
2.3. with TempoClock
(
~tempo = TempoClock;
~tempo.tempo = 1;
//s.doWhenBooted({
s.waitForBoot({
{ Window.closeAll }.defer;
600.do { |i|
{ w = Window().background_(Color.rand).front }.defer;
i = if (i % 4 == 0) { 12 } { 0 }.postln;
x = { SinOsc.ar((81 + i).midicps) * Env.perc.ar(Done.freeSelf) * [0.1, -0.1] }.play;
1.wait;
{ w.close }.defer
}
}, clock:~tempo)
)
~tempo.tempo = 30/60
Could it be acceptable? This
If AppClock is the default, this does not imply any problems with the existing code.
I have some projects for stage performance use that donât use any GUI, and some people in some situations might be glad to start the entire project with one main file being loaded with just those methods.
It could be somewhat educative too to provide options.
But itâs not a big deal! Iâm just giving a perspective.
I closed the initial PR (Topic/mention default clock for s.waitForBoot and s.doWhenBooted by prko ¡ Pull Request #6193 ¡ supercollider/supercollider ¡ GitHub) and made a new one: