Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix inconsistent state of clock when tempo is set via setTempoAtSec #2078

Merged
merged 6 commits into from
Jun 8, 2017

Conversation

telephon
Copy link
Member

This fixes #2077.

Someone with deeper insight into clock scheduling could check if the explanations in the helpfile are ok.

telephon added 3 commits May 11, 2016 19:18
notification of dependants also happens when tempo is set via
`etempo_()`.
an inconsistency can happen when the clock’s tempos was set to <= 0 via
`etempo_()` (prTempoClock_SetTempoAtTime), and then later one tries to
set it again to a new value via `tempo_()`
(prTempoClock_SetTempoAtBeat). Then beats become `nan`. This protects
`tempo_()` from being called in such a situation.
Explains the method `etempo_()`
@telephon telephon changed the title Topic tempo clock stop fix inconsistent state of clock when tempo is set via setTempoAtSec May 11, 2016
@muellmusik
Copy link
Contributor

Forgive my misunderstanding, but could you explain why we need to be able to set a TempoClock's tempo to <= 0 at all?

I think the documentation is still a bit unclear, and the word 'assuming' not specific enough. But then the methods are poorly named IMO.

Probably this is what you meant, and understand, but to be clear, TempoClocks use a base time and base beats to calculate current beats. So their 'memory' only goes back to the last tempo change. What setTempoAtBeat and setTempoAtSecs do change the tempo and the base values. This can mean that the current beats may change as a side effect, which is dangerous. Any documentation should make all this clear. But really, I think these are actually private methods. I'm not sure why users would need them.

@telephon
Copy link
Member Author

The method that is the "easier" interface is etempo. This can be set to zero or negative. Then the clock stops, or runs backwards. "beats" is just a real number, as it seems, and TempoClock can function like a timeline controller, as it seems.

I stumbled upon it because I wanted to be able to set the tempo to zero to pause the clock. This works with etempo_, but not with tempo_. When tempo is zero, you can move the "clock position" around, by setting beat = ….

@jamshark70
Copy link
Contributor

My gut feeling is that this needs thorough testing before being recommended as a way (or the way) to move clock time arbitrarily.

It happens way too often in SC-land that we think of a clever way to do something without properly implementing it as a feature, then push ahead with it before examining the implications... and then it breaks sometime down the road and we have to stick little band-aids onto what was a hack to begin with.

Let us please not repeat that mistake here. No problem to have a branch for people to play with and find bugs, but IMO this is very much in progress and not ready to merge.

@telephon
Copy link
Member Author

@jamshark70 yes, agreed. So I'll remove the example from the helpfile? The rest of the patch has nothing to do with it, it just protects the clock state.

@muellmusik
Copy link
Contributor

Yes, I was going to say I also have some reservations about the idea of 0 or negative tempi, because it would commit us to a new and probably rarely used functionality, and could thus constrain us in the future.

There's a question of separation of concerns, i.e. what is a Clock? Yes, clocks in SC share some characteristics with timelines, in that you can schedule callbacks on them and thus use them as a simple event queue, but I think they are primarily there to monitor the passage of time, and allow other objects (functions, etc.) to register for notifications of particular times passing.

That seems sufficient to me (though convince me otherwise) and I think timeline functionality is better suited to other objects, like Routines, Tasks, and more elaborate objects. Negative tempi won't IIUC allow for time to flow backwards in the current implementation anyway, as clocks and their callback queues have no memory.

Whether a TempoClock should be 'pause-able' is perhaps more interesting. Of course Tasks and Routines can pause, but they have no memory of what point between events they paused at, and resume immediately with the next scheduled event. True pausing between events is a desirable behaviour. That should possibly be supported via an alternative method in Task, or similar handling in higher level classes. If it was supported in TempoClock, I'm not sure that etempo = 0 is the way to do it. A proper pause method might be cleaner.

If setTempoAtBeat/Sec are documented, I think they should be clearly labelled as private/internal.

@crucialfelix crucialfelix added this to the 3.9 milestone May 12, 2016
@telephon
Copy link
Member Author

Actually, I found it inconsistent that you can set the clock to tempo 0.0000001, but not to 0.

@muellmusik
Copy link
Contributor

Actually, I found it inconsistent that you can set the clock to tempo 0.0000001, but not to 0.

Well, I guess I'm just asking what's the use case, and what's the best way to do it.

@telephon
Copy link
Member Author

telephon commented May 12, 2016

I wanted to use the clock by hand, moving it by explicitly setting the beats.

Btw. here are some tests. But keep in mind that this is not inside a Thread or anything, I haven't tested any of that yet.


// this works as "expected"
t = TempoClock.new; t.etempo = 0;

t.schedAbs(1, { 1.postln; nil });
t.schedAbs(2, { 2.postln; nil });
t.schedAbs(2.5, { 2.5.postln; nil });

t.etempo = 1;



// this works as "expected"
t = TempoClock.new; t.etempo = 0;

t.schedAbs(1, { 1.postln; nil });
t.schedAbs(2, { 2.postln; nil });
t.schedAbs(2.5, { 2.5.postln; nil });

t.beats = 1;
t.beats = 3;


// this works as "expected"
t = TempoClock.new(beats: -10); t.etempo = 0;

t.schedAbs(-1, { -1.postln; nil });
t.schedAbs(-2, { -2.postln; nil });
t.schedAbs(-2.5, { -2.5.postln; nil });

t.etempo = 1;



// this doesn't work as "expected"
// it seems that you have to pass the events in a "forward" direction

t = TempoClock.new; t.etempo = 0;

t.schedAbs(-1, { -1.postln; nil });
t.schedAbs(-2, { -2.postln; nil });
t.schedAbs(-2.5, { -2.5.postln; nil });

t.etempo = -1;

@muellmusik
Copy link
Contributor

// this doesn't work as "expected"
// it seems that you have to pass the events in a "forward" direction

Yes, so it would need to be changed. But again, why does one need TempoClock to support negative beat values and negative tempi? If we add that, we're limiting the ways the implementation might be changed or optimised in the future.

I think the 'moving by hand' idea could be fairly easily accomplished with something like a PriorityQueue. You could move around, or play, but without needing to alter the basic lower level clock functionality, or give it a memory. Does that make any sense? I just feel like this sort of abstracted event list is higher level than a clock, and likely more a has-a than an is-a relationship.

@jamshark70
Copy link
Contributor

I just feel like this sort of abstracted event list is higher level than a clock, and likely more a has-a than an is-a relationship.

... making the "do it all in the clock" approach just the sort of antipattern we've historically loved in SC :p

Note that SC's queues give you access to the next upcoming item only. An abstracted event list needs to be a separate thing.

@muellmusik
Copy link
Contributor

Note that SC's queues give you access to the next upcoming item only. An abstracted event list needs to be a separate thing.

Yes. In SST, I just used an array, but something smarter would be nice.

@telephon
Copy link
Member Author

I share your reservations.
The question of what to do in such situations is not so simple, however. We have a lot of implementations that could be generalized to a more general use scenario. Whether this is good or bad needs to be seen for each case.

@telephon
Copy link
Member Author

I've removed all debatable documentation: eaac100

The rest is just a protection against getting into the wrong state.

@vivid-synth vivid-synth added the comp: class library SC class library label Aug 3, 2016
@adcxyz
Copy link
Contributor

adcxyz commented Nov 28, 2016

Protecting clocks against setting them to illogical state seems like an improvement to me. So ok to merge?

@muellmusik
Copy link
Contributor

Sorry, I've completely lost the plot on this some months later. Could we just have summary of what exactly this is intended to do now with all the changes? (Sorry, a bit braindead, but worried as this is so core.)

Could we also use this as an opportunity to document things like etempo? It makes reviewing these things a real pain that there are all these undocumented methods.

@telephon
Copy link
Member Author

There is a help entry for etempo in this PR, I don't know maybe it is not enough?

fd5f440

@muellmusik
Copy link
Contributor

There is a help entry for etempo in this PR, I don't know maybe it is not enough?

Sorry Julian! That's great. For my addled brain could somebody just sum up what this is doing now? I gather from the discussion that it's less ambitious then before, but I admit I don't quite understand

@telephon
Copy link
Member Author

it allows you to set the clock's tempo to zero ( unlike now where you have to set it to a very very very small value). When I looked at the code i also saw that there is nothing that should prevent us from setting it to a negative value either.

@telephon
Copy link
Member Author

There seems to be consensus.

@telephon
Copy link
Member Author

But if necessary, it can also be postponed.

@telephon telephon requested a review from mossheim May 27, 2017 19:42
@LFSaw
Copy link
Member

LFSaw commented May 27, 2017

I checked it out, compiled and tried the examples in the help file including

t= TempoClock.new;

(
t.sched(3, {
	"baaaa".postln;
	nil
});
)


t.etempo = 0


// nothing happens...
(
t.sched(3, {
	"baaaa".postln;
	nil
});
)
t.etempo = 1

/// noooow

Just one thing: I searched (and failed to find) information on "logical time" vs. "elapsed time". Maybe these can be linked in the helpfile?

@jamshark70
Copy link
Contributor

I searched (and failed to find) information on "logical time" vs. "elapsed time"

Perhaps add some keywords to the Server Timing help file (for searching -- links would be good too).

@LFSaw
Copy link
Member

LFSaw commented May 28, 2017

merge it, I say.

@mossheim mossheim self-assigned this Jun 5, 2017
Copy link
Contributor

@mossheim mossheim left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This was quite simple when I finally figured out what was going on. This small snippet was very illuminating to me:

(
r {
	t = TempoClock.new;
	t.sched(3, {
		"baaaa".postln;
		nil
	});
	t.etempo = -1;
	"going 6 seconds back in time.".postln;
	6.wait;
	t.etempo = 3;
	"in ".post;
	for(3, 1) { |i|
		"%...".postf(i);
		1.wait;
	};
}.play;
)

@mossheim mossheim merged commit b4d3880 into supercollider:master Jun 8, 2017
@mossheim
Copy link
Contributor

mossheim commented Jun 8, 2017

Note: I did a rebase merge here. This PR is very old and has a mix of recent and old commits, so I didn't want to add noise to the project history. At the same time, squashing didn't seem appropriate since a few of the commits have different purposes (SC style vs cpp changes vs help changes)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
comp: class library SC class library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants