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

cancelScheduledValues with setValueCurveAtTime #2071

Closed
rtoy opened this issue Oct 2, 2019 · 7 comments
Closed

cancelScheduledValues with setValueCurveAtTime #2071

rtoy opened this issue Oct 2, 2019 · 7 comments
Assignees
Projects
Milestone

Comments

@rtoy
Copy link
Member

rtoy commented Oct 2, 2019

Describe the issue
Consider the following bit of code:

time = ctx.currentTime;
gain.gain.setValueCurveAtTime(curve, time + 0.1, 1);
gain.gain.cancelScheduledValues(time + 0.2);
gain.gain.setValueCurveAtTime(curve, time + 0.3, 1);

The second call to setValueCurveAtTime will throw an error because it overlaps the first. Basically cancelScheduledValues didn't remove the first setValueCurveAtTime.

A strict reading of the spec says this is the correct behavior because the event time of the first setValueCurve is time + 0.1, which is before the cancel time of time + 0.2. Hence it isn't removed.

The result is that you can't actually remove a setValueCurve; you have to wait until after the setValueCurve has ended before scheduling anything else.

The spec is pretty clear on what the behavior is, but I think we do really want to be able to remove the setValueCurve event if it ends some time after the cancel time.

setTargetAtTime kind of has the same issue, but you can always schedule events after it without errors, so perhaps we don't need to do anything here for that.

Where Is It
See cancelScheduledValues

Also see StackOverflow question on this.

@chrisguttandin
Copy link
Contributor

Wouldn't it be possible to use cancelAndHoldAtTime() instead of cancelScheduledValues()? It will truncate the curve which makes it possible to schedule another curve right away.

@rtoy
Copy link
Member Author

rtoy commented Oct 3, 2019

Yes, cancelAndHoldAtTime would do something useful. Not quite the same as cancelScheduledValues, though.

The alternative that would work is to use the same time for cancelScheduledValues as was used for the setValueCurveAtTime. Maybe that is the answer we're looking for here?

@mogelbrod
Copy link

Author of the StackOverflow question here.

From the cancelScheduledValues() spec:

Cancels all scheduled parameter changes with times greater than or equal to cancelTime. Cancelling a scheduled parameter change means removing the scheduled event from the event list. Any active automations whose automation event time is less than cancelTime are also cancelled, and such cancellations may cause discontinuities because the original value (from before such automation) is restored immediately. Any hold values scheduled by cancelAndHoldAtTime() are also removed if the hold time occurs after cancelTime.

My interpretation of the bolded part is that setValueCurveAtTime(curve, t, 1) should be aborted by cancelScheduledValues(t + 0.1), unless value curves aren't considered automations? If that's the case it doesn't appear to be possible to cancel already started curves, since the cancelScheduledValues(cancelTime) argument is clamped to >=currentTime:

If cancelTime is less than currentTime, it is clamped to currentTime.

const start = ctx.currentTime
gain.gain.setValueCurveAtTime(curve1, start, 1)
setTimeout(() => {
  const cancelTime =  ctx.currentTime // both `0` and `start` are clamped to ctx.currentTime according to the spec
  gain.gain.cancelScheduledValues(cancelTime)
  gain.gain.setValueCurveAtTime(curve2, ctx.currentTime, 1)
}, 100)

Current browser behaviour for the above code

Firefox v68

Throws Operation is not supported when cancelTime is 0, start or ctx.currentTime.
Haven't found any working values.

Chrome v77

Any value less than ctx.currentTime appears to work (0, start, ctx.currentTime - 0.01).
Using ctx.currentTime throws Failed to execute 'setValueCurveAtTime' on 'AudioParam': setValueCurveAtTime(...) overlaps setValueCurveAtTime(...).

Edge v42

All values 0 <= cancelTime <= ctx.currentTime appear to work.

@rtoy
Copy link
Member Author

rtoy commented Oct 3, 2019

Oh, you are right about the bold part of the spec. I missed that on my initial read. I think that means setValueCurveAtTime event will be removed as well as a setTargetAtTime. That will make your use case work better.

However, I'm not sure what should happen if the setValueCurveAtTime is being processed when cancelScheduledValues is called and the setValueCurve event is removed. What is the param value then? Same question for setTargetAtTime.

@rtoy
Copy link
Member Author

rtoy commented Oct 10, 2019

Teleconf: @rtoy to come up with a proposal on how this works. Probably will remove the setCurve if the cancel time lies between the start and duration of the setCurve. Not a problem for setTarget where you can always schedule something after a setTarget.

@rtoy rtoy added this to Untriaged in V1 via automation Oct 30, 2019
@rtoy rtoy moved this from Untriaged to Ready for Editing in V1 Oct 30, 2019
@rtoy
Copy link
Member Author

rtoy commented Oct 30, 2019

I think we need to consider what it means to be an "active automation".

My interpretation is that at the time cancelScheduledValues is called the automation is running, and we're canceling events that includes the the currently running event. Something like the following (that can be run using https://hoch.github.io/canopy):

// @channels 1
// @duration 1.0
// @sampleRate 44100

var osc = new ConstantSourceNode(context);
var gain = context.createGain();

osc.offset.setValueAtTime(0, 0.0);
osc.offset.linearRampToValueAtTime(2, .5);
context.suspend(.3)
.then(() => osc.offset.cancelScheduledValues(.4))
.then(() => context.resume());

osc.connect(gain).connect(context.destination);

osc.start();

At time 0.3, we call cancelScheduledValues(0.4) and the resulting
curve stops increasing and holds at the value it had at time 0.3.

But if we changed the example to

osc.offset.setValueAtTime(0, 0.5);
osc.offset.linearRampToValueAtTime(2, 1);
context.suspend(.3)
.then(() => osc.offset.cancelScheduledValues(.75))
.then(() => context.resume());

the output is a constant 1 up to time 0.5 and then becomes a constant 0.

For setValueCurve, I think it might be best if cancelScheduledValues is called before the start of the setValueCurve, and if the cancel time is in the middle of the setValueCurve, the entire curve event is removed.

If cancelScheduledValues is called when the curve is running, and the cancel time is in the middle of the curve, maybe the right thing would be to remove the curve event, and then replace it with a setValueAtTime event whose value is equal to the curve output at the cancel time. This is a bit complicated, but kind of matches how the linearRamp example above works.

@mdjp mdjp added this to the Web Audio V1 milestone Nov 21, 2019
@rtoy
Copy link
Member Author

rtoy commented Dec 6, 2019

The behavior in the first snippet from #2071 (comment) isn't specified any where that I can find. I don't want to specify that now, so I'm not going to add anything about that for setValueCurve.

But I will make it explicit that if the cancel time is in the middle of the curve, the entire curve event is removed.

rtoy added a commit to rtoy/web-audio-api that referenced this issue Dec 6, 2019
If the cancelTime overlaps the setValueCurveAtTime event, remove the
event too.
@rtoy rtoy moved this from Ready for Editing to In PR Review in V1 Dec 12, 2019
@rtoy rtoy closed this as completed in 9d4d866 Mar 19, 2020
V1 automation moved this from In PR Review to Done Mar 19, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
V1
  
Done
Development

No branches or pull requests

5 participants