WebAudio / web-audio-api

The Web Audio API v1.0, developed by the W3C Audio WG
https://webaudio.github.io/web-audio-api/
Other
1.06k stars 168 forks source link

Add a method to cancel AudioScheduledSourceNode.stop() #2451

Open bgins opened 5 years ago

bgins commented 5 years ago

Describe the feature I think that a method to cancel a AudioScheduledSourceNode.stop() would be useful. The method would put the AudioScheduledSourceNode into a playing state indefinitely until a subsequent call to stop() schedules a new stop time.

Is there a prototype? No.

Describe the feature in more detail Currently, the only way to extend the time that an AudioScheduledSourceNode will play is a call to stop() with a time farther into the future than any previous call to stop(). In some cases, it may be difficult to say for certain when the AudioScheduledSourceNode should stop playing. We may need to wait for a user to take action to know when to stop.

For example, consider an OscillatorNode connected to a GainNode with an envelope (from an envelope generator library) attached to GainNode.gain. Suppose that a user starts and stops the oscillator from a Midi controller or their computer keyboard. When a user presses a key, the oscillator starts, and when they release the key, we schedule the oscillator to stop once the release phase of the envelope has ended.

If a user presses the same key before the release phase has ended, we want to retrigger the envelope and keep the oscillator playing for an indefinite period of time (until the user releases the key again and the envelope's new release phase has ended).

From what I can tell, the only way to do this is to call stop() with some huge value assuming that no user would hold a note that long. This is a little unintuitive, and it takes some research for a developer to decide that this is the way to effectively "cancel" a stop().

I think a call to cancel a stop() should only happen between the time stop() is called and the time the queued control message to stop has taken effect.

chrisguttandin commented 5 years ago

You mentioned that you could call stop() with a very large value. That got me thinking. What happens if you call it with an infinite value? If that would be possible it would effectively cancel the previous call to stop().

I was curious how the browsers do currently behave because the definition of the stop() method does not explicitly disallow infinite values. However I get an error in Firefox and Chrome when I run the following example.

const ctx = new AudioContext();
const osc = new Oscillator(ctx);

osc.connect(ctx.destination);

osc.start();
osc.stop(ctx.currentTime + 10);
osc.stop(Number.POSITIVE_INFINITY); // This throws an error.

Both browsers complain that when is non-finite and stop the Oscillator after 10 seconds.

A lot of other functions like setValueAtTime() and linearRampToValueAtTime() do explicitly mention that they don't accept infinite values but the stop() method doesn't have that requirement.

bgins commented 5 years ago

Calling stop() with Number.POSITIVE_INFINITY makes sense to me. It reads like play forever. If the spec intends this as a possibility, it might be good to mention it.

rtoy commented 5 years ago

I don't know why certain methods say they don't accept infinite values, but https://heycam.github.io/webidl/#idl-double makes it pretty clear that double means finite values and 'unrestricted double` allows non-finite and NaN values.

On the face of it, this seems like a reasonable way to cancel a scheduled stop.Or I guess you can use just some huge value. In practive any value over 2^64 frames is the limit since all (most?) implementations use a size_t to count frames instead of a double to count time.

rtoy commented 5 years ago

Teleconf: Leave things as is. The user should set a large (finite) value that basically cancels the original stop.

bgins commented 5 years ago

Any details on the reasoning behind this? When I set a large finite value far into the future, I think to myself that I must be missing a better method to do this. In part, this is because the AudioParam interface provides cancel methods, and I expect that AudioScheduledSourceNode would have something similar.

rtoy commented 5 years ago

There's a bit of a difference from AudioParam because you can schedule a huge number of events in the future. And without a cancel method, there would be no way to remove them.

The basically conclusion is that it works. Adding an explicit cancellation method doesn't really help. It was suggested that allowing infinity (and/or NaN) would make the intention clear, but the value added in specifying this was deemed too small when the developer could just say stop(1e300). Not great, but there it is. 5e14 sec is already greater than age of the universe.

bgins commented 5 years ago

Alright, thank you for the explanation.

rtoy commented 5 years ago

F2F: after further discussion with @bgins, re-opening.

bgins commented 5 years ago

I was thinking about this issue after the F2F. I think a decent name for this method would be cancelScheduledStop. This is somewhat along the lines of cancelScheduledValues, but also indicates that only one scheduled event will be cancelled (the last invocation of stop).

Also, we discussed whether this method is syntactic sugar for scheduling a stop far into the future. I am not sure whether this is the case. The spec says that:

An AudioScheduledSourceNode is said to be playing when its associated BaseAudioContext's currentTime is greater or equal to the time the AudioScheduledSourceNode is set to start, and less than the time it’s set to stop.

If an AudioScheduledSourceNode has been set to start, but no stop is ever called, does it implicitly have a stop time? If so, then I think the method would be syntactic sugar.

If not, I think this method might do something different, like resetting the node to a state as if no stop had ever been called.

padenot commented 3 years ago

AudiWG Virtual F2F: