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

EventStreamPlayer could easily support hanging on Conditions #4967

Open
eleses opened this issue May 25, 2020 · 3 comments
Open

EventStreamPlayer could easily support hanging on Conditions #4967

eleses opened this issue May 25, 2020 · 3 comments

Comments

@eleses
Copy link
Contributor

eleses commented May 25, 2020

Motivation

Presently

d = Pbind(*[dur: 0.3, degree: Pseries(0, 1, 8)])
q = Condition.new;
(Prout({|ev| q.hang; loop { ev = ev.yield } }) <> d).play;
// Err: 'hang' doesn't know how to "playAndDelta"

Description of Proposed Feature

Well, EventStreamPlayer could easily support hang by just yielding that when it sees it as an event.

Plan for Implementation

Quickly tested with this derivative:

EventStreamPlayerH : EventStreamPlayer {

	prNext { arg inTime;
		var nextTime;
		var outEvent = stream.next(event.copy);
		case
		{outEvent.isNil} {
			streamHasEnded = stream.notNil;
			cleanup.clear;
			this.removedFromScheduler;
			^nil
		}
		{outEvent === \hang} { ^outEvent } // the only addition basically
		{
			nextTime = outEvent.playAndDelta(cleanup, muteCount > 0);
			if (nextTime.isNil) { this.removedFromScheduler; ^nil };
			nextBeat = inTime + nextTime;	// inval is current logical beat
			^nextTime
		};
	}
}

And an actual test for that code, since I used a different class "just in case".

q = Condition.new
p = (Prout({|ev| q.hang; loop { ev = ev.yield } }) <> d)

r = EventStreamPlayerH(p.asStream, ()).play

q.unhang

Seems to work ok for me. I can submit a pull req on this as an actual patch to EventStreamPlayer proper if there are no objections to these minor semantics improvement, i.e. making it support hangs (via Conditions).

Looking at prTempoClock_Sched (C++) it seems anything that's not a double will be treated as "no op", not just \hang, so probably the test I've added to EventStreamPlayer

case ... {outEvent === \hang} { ^outEvent } 

could be made more general to yield/return any "rubbish" that doesn't respond to playAndDelta "as is". But doing this could obscure some bugs in user code, i.e. instead of getting an error due to playAndDelta on a a buggy stream (that was meant to return events but somehow doesn't) it will be silent when played. Interestingly SimpleNumber does define playAndDelta as { } thus returning itself, so a number can be used as "rest" in an event stream (due to how sched works and because the addition nextBeat = inTime + nextTime is fine for numbers):

(Prout({|ev| 3.yield; loop { ev = ev.yield } }) <> d).play // 3 sec delay to start
@telephon
Copy link
Member

telephon commented May 25, 2020

Just to be sure I get this right:

1.) We would gain a way to halt an event stream in the middle, without thereby clearing the cleanup. This is like what can be done with the message pause from the outside, but from within the stream.

2.) The return value of the event stream should signal what will happen. Apart from the returned event being played, it is only variants of resting (essentially time values) or finishing up (nil).

But because we can encode any functionality in the event returned, we can simply call your function from an event. An event is really just a function with default arguments that is called on play (or playAndDelta).

This works:

(
d = Pbind(*[dur: 0.3, degree: Pseries(0, 1, 8)])
q = Condition.new;
(Prout({|ev| (play: { q.hang }).yield; loop { ev = ev.yield } }) <> d).play;
)

q.unhang;

Do you see a reason why this won't do it?

@eleses
Copy link
Contributor Author

eleses commented May 25, 2020

1.) Yes, that's the point. 2.) And yes, that's fairly valid workaround, but the duration (implicit 1) is still contained in the custom-play Event, and the next event is delayed by that much (1 sec, in your example); if you make it (play: { q.hang }, dur: 3) it's a bit more obvious. (That's happening because changing the Event's play does not override what Event.playAndDelta does with respect to the timing of the next event, and it's the latter method that's called from the EventStreamPlayer, unlike when you play an event directly/"manually".) But one could make the dur very short. Actually, I just tried it with (play: { q.hang }, dur: 0) and it works; usually dur: 0 is a bomb, but not here.

For a bit more context, the reason I was doing this is that if you use Pgate to achieve the same thing (with an initial rest) you have exactly the same problem, that you get an initial delay when "unhanging"; albeit you could make that one pretty short too. But with Pgate it's actually a worse problem, because it spams the initial rest until you enable the gate, so you can't make the rest way short, which you can with your custom-play solution. The Pgate thing that motivated this:

e = ();
Pgate(Pseq([Rest(0.1), d]), 1, \go_d).play(protoEvent: e);
e[\go_d] = true;

@jamshark70
Copy link
Contributor

usually dur: 0 is a bomb

It's a bomb when looping infinitely over a constant delta (which is admittedly a common case), but hanging/unhanging a thread is not an infinite loop.

One could create a \hang event type whose parent event specifies a default delta of 0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants