"permanent" routine?

Hello

I’d like to make my looping, GUI-updating, routine “permanent” i.e. resisting cmd-period.

I’ve gone round in circle around this (toy example here)

w = Window.new.front;
t = StaticText(w,Rect(0,0,200,20));
q = TempoClock(1).permanent_(true).schedAbs(0, {defer{t.string_(1.0.rand)}; 0.5});

Why does it stop at cmd-period ? what am I missing? pointers welcome

So, as far as I can tell, TempoClock isn’t supposed to do that.

When cmdPeriod is run TempoClock keeps the permanent clocks alive, but then stops all the scheduled events regardless.

*cmdPeriod {
	all.do({ arg item; item.clear(false) });  // this line stops all scheduled events
	all.copy.do({ arg item; if (item.permanent.not, { item.stop })  })
}

Fix

If you add a new member to TempoClock

	var <>keepChildrenAfterCmdPeriod=false;

and alter TempoClock*cmdPeriod

	*cmdPeriod {
		all.do({ |item|
			if((item.permanent and: item.keepChildrenAfterCmdPeriod).not) {
				item.clear(false)
			}
		});
		// copy is important: You must never iterate over the same
		// collection from which you're removing items
		all.copy.do({ arg item; if (item.permanent.not, { item.stop })  })
	}

Now it works as expected…

w = Window.new.front;

t = StaticText(w, Rect(0, 0, 200, 20));

TempoClock(1)
   .permanent_(true)
   .keepChildrenAfterCmdPeriod_(true)
   .schedAbs(0, { defer{ t.string_(1.0.rand) }; 0.5 });

Do you think it would be worth making an issue for this? Seems like a reasonable and common thing to want to do.

thanks @jordan for this!

I would be happy to, if there are no other way to invoke permanent like one can do on a scheduled task (I reckon that the permanent flag is for the new TempoClock itself, right?)

I think permanent is just intended to keep the clock alive, not scheduled tasks.
The obscurely-named SkipJack class is the built-in way to run a routine that survives cmd+period. It’s slightly high-level, but you can look at the source to see how it implements the behavior if you want to do some lower-level things (I think all it’s doing is just playing itself again on cmd+period).

(ah yes, the default TempoClock is permanent, so DEFINITELY this change of behavior would break a ton of stuff, since it would mean no musical routines are ever stopped by cmd+period)

Its opt in (unless your looking at a previous version I posted for about half a second!), it should’t change anything unless the user wants it to I think?

SkipJack isn’t quite the same though as it calls the function whenever you press cmd+., meaning, if you had musical stuff here, it would get triggered immediately. Probably fine for GUI code, though.

@scztt reading SkipJack’s class is indeed a great tutorial.

this works perfectly:

w = Window.new.front;
t = StaticText(w,Rect(0,0,200,20));
q = SkipJack({t.string_(1.0.rand)}, 0.5);

indeed - won’t work well for anything more time-sensitive… so the mod would be a great improvement.

I think the intention of the CmdPeriod class was that, if you have something specific to restore after cmd-., you register it yourself, here.

That is, it’s already possible to handle it in the class library as it is, without any changes at all. It could be made more convenient, sure.

EDIT: It’s easy to restore a scheduled task after cmd-dot if you don’t mind the time sequence not being maintained (as in SkipJack). To maintain the time sequence, currently it’s rather ugly, but it’s possible to retain nextBeat and re-schedule for that time.

(
t = Task {
	loop {
		thisThread.clock.beats.postln;
		// nextBeat is not known until after `wait`
		defer { ~saveBeat = t.nextBeat };
		1.0.wait;
	}
}.play;
)

CmdPeriod.add({
	t.clock.schedAbs(~saveBeat, t.refresh)
});

A good solution would be to make this easier. Perhaps PauseStream incorporates saveBeat, and it could also implement doOnCmdPeriod to relaunch. In that case you’d just write CmdPeriod.add(t) and done, and the property of “relaunch on cmd-.” would be per task rather than per clock.

(end EDIT)

We tend to schedule a lot of different things on the same TempoClock, so I don’t think I’d favor an all-or-nothing flag on the clock. Continuation or not would be a property of the thing being scheduled, not of the scheduler.

EDIT again: The problem with the clock approach is that it will be a common case to want to schedule non-continuing and continuing tasks with the same tempo and meter. If the property is per clock, then we would have to tell users to create a second clock for the continuing tasks, and synchronize tempo and meter changes between the two clocks :face_with_monocle: That doesn’t feel like an especially good design for a common case, so I think other solutions would be preferable.

hjh

if one could assign .permanent_(true) to a forked looped function that would feel to me the most intuitive… I’m saying this as I know I’m wrong, but this is where I’m at with my understanding of mid-level SC so I share candidly. I really appreciate the permanent aspect of the various *def stuff (HIDdef, OSCdef, MIDIdef) when I need it and I feel I can leave that ‘alive’ - classic UI stuff - hence the thinking in similar veins for GUI updating (of counters, timers, control bus vues, etc)

p

ps I hope this feels ok to comment on learning process here. I keep saying to the FluCoMa team that “you only learn for the first time once” so I always valued (advanced) verbose learning processes to understand the blockages… let me know if it is just noisy here.

To be fair, I don’t use clocks (doesn’t fit the way I want to think about time) so I assumed making a new clock for the things you want to keep alive would not be a problem. In the future it might be worth investigating relationships between clocks, do you could synchronize them, or have them slowly drift away from each other.

By the way, does anyone have a better name than skipJack? Might be worth making an alias.

You probably want to pass it into tempoclock …

TempoClock().schedAbs(0.5, {...}, suriveCmdPeriod: true)

…otherwise all objects would have to store their permanence.

The documentation on TempoClock:permanent could certainly be improved! I’m assuming the intention here is to avoid a greasy buildup of TempoClocks doing nothing when frequently stopping? Should just say so.

I’d suggest just flagging it at some appropriate places in the doc (e.g. TempoClock:permanent). It’s whimsical, but makes sense, and there’s a bit of a tradition of that.

@tremblap I’d just say maybe be careful here about design. There have been many attempts over the years to make CmdPeriod smarter, and they usually end unsustainable complication. I’m not opposed to a way to make this easier (though SkipJack is okay for many things), but I’ve always strongly felt that Cmd-. is best understood as a panic button / kill switch. If the problem is it’s too coarse grained, then the solution is often to make a more fine grained way to stop stuff. :slight_smile:

Yes, the reason for the automatic stopping of TempoClocks (and auto-removal of OSCFunc / MIDIFunc etc) is because it’s fairly common to run init code and create these resources, then cmd-dot halt, edit the code, and run the init code again. So the default position is to remove the resources.

I would prefer something like this over a clock property. I’m leery of adding a member variable to every Function object that exists everywhere in the system, just for the small minority that you might schedule on the clock and want to be permanent. (The title of the thread says “routine,” but the initial code example doesn’t use a routine – it’s a function that returns a number – so presumably we have to handle that case as well.)

HOWEVER… I can’t remember any time in the last 20 years that a user requested the ability to persist a task through cmd-period while maintaining exact timing. To my knowledge, nobody has ever wanted to continue musical activity through cmd-dot. (If they did, they found their own way and didn’t raise an enhancement request for it.) Scott says “Cmd-. is best understood as a panic button / kill switch” and certainly if DSP has gone haywire, you don’t want cmd-dot to throw more DSP into the server.

That is… it’s a tricky design problem = a fair amount of effort, for something that nobody has raised as a super-important feature. (Even in your case – your initial post doesn’t state that maintaining timing is a critical requirement. It’s just a suggestion from Jordan, which you labeled as “might be nice,” but I don’t see anyone here saying “this is a showstopper.”)

If I’m reading that correctly, then it might be better to reserve development time for more important tasks.

It’s not a huge problem but “use two clocks instead of one” isn’t going to sit well with a lot of users.

Also there’s only one AppClock – so a keepChildrenAfterCmdPeriod_ flag for AppClock wouldn’t have sufficient granularity.

Another way to model this, btw, is through object composition: a PermanentTask wrapper around a Routine, Task or Function.

hjh

ok thanks all. I think the proposed solution with SkipJack is perfect for my case, and the more involved CmdPeriod world is also good for most.

This thread might be useful for future lost puppies like me, so I’ve declared a solution and readers/learners will catch it up right away.

thanks again! so much to learn!

And FWIW, it’s perfectly fine to bring it up and discuss – at the same time, a similar feature request in my old software company job would have had a low “pain index” and program managers would have treated it as low priority. It’s reasonable to assess the actual urgency before devoting resources.

hjh

1 Like