Skip to content

cancelScheduledValues with setValueCurveAtTime #2071

Closed

Description

@rtoy

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.

Activity

  1. chrisguttandin commented on Oct 2, 2019

    @chrisguttandin
    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.

  2. rtoy commented on Oct 3, 2019

    @rtoy
    MemberAuthor

    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?

  3. mogelbrod commented on Oct 3, 2019

    @mogelbrod

    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.

  4. rtoy commented on Oct 3, 2019

    @rtoy
    MemberAuthor

    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.

  5. rtoy commented on Oct 10, 2019

    @rtoy
    MemberAuthor

    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.

  6. added
    Needs EditsDecision has been made, the issue can be fixed. https://speced.github.io/spec-maintenance/about/
    on Oct 30, 2019
  7. rtoy commented on Oct 30, 2019

    @rtoy
    MemberAuthor

    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.

  8. added this to the Web Audio V1 milestone on Nov 21, 2019
  9. rtoy commented on Dec 6, 2019

    @rtoy
    MemberAuthor

    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.

  10. added a commit that references this issue on Dec 6, 2019
    866d369
  11. added and removed
    Needs EditsDecision has been made, the issue can be fixed. https://speced.github.io/spec-maintenance/about/
    on Feb 13, 2020
  12. added a commit that references this issue on Nov 23, 2025
    9d4d866
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions