【问题标题】:WebAudio: setTargetAtTime() event is triggered Immediately, not at Scheduled TimeWebAudio:立即触发 setTargetAtTime() 事件,而不是在预定时间触发
【发布时间】:2020-08-04 04:50:13
【问题描述】:

我有一个 振荡器,它连接到一个 增益节点,用于创建音量包络,随着时间的推移逐渐降低增益。 在开始后大约 1.5 秒,我想在 Second 2 执行以下操作:

  • 取消所有计划的(未来)包络增益更改。
  • 创建一个新的时间表 setTargetAtTime 以“淡出”声音。

问题是“淡出” setTargetAtTime() 似乎立即被触发,这使得 JUMP IN VOLUME。是的,如果第二个参数 (startTime) 小于或等于 audioContext.currentTime,这是预期的。但这里不是这样。

function startSound() {
  //Audio context
  var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  var currentTime = audioCtx.currentTime;

  //Create Gain Node And Oscillator
  var gainNode = audioCtx.createGain();
  var oscillator = audioCtx.createOscillator();
  oscillator.type = "sine";
  oscillator.frequency.setValueAtTime(440, currentTime);

  //Connect Oscillator to Gain Node and Gain Node to audioCtx.destination
  oscillator.connect(gainNode);
  gainNode.connect(audioCtx.destination);
  oscillator.start(currentTime);

  //Create Gain (Volume Envelope)
  gainNode.gain.setValueAtTime(0.5, currentTime);
  gainNode.gain.linearRampToValueAtTime(0.5, currentTime + 0.5);
  gainNode.gain.linearRampToValueAtTime(0.25, currentTime + 2);
  gainNode.gain.linearRampToValueAtTime(0.000001, currentTime + 5);
  gainNode.gain.linearRampToValueAtTime(0.000001, currentTime + 999);

  //Approximately 1.5 seconds later, Cancel ALL Scheduled Envelope changes from second 2 and up, and create a FAST
  //"Fade Out" that lasts 0.1 seconds.
  var cancelAt = currentTime + 2;

  setTimeout(function () {
    StopEnvelope(audioCtx, gainNode, cancelAt);
  }, 1500);
}

function StopEnvelope(audioCtx, gainNode, cancelAt) {
  //This is never TRUE
  if (cancelAt <= audioCtx.currentTime) {
    console.log("cancelScheduledValues will happen immediately!");
  }

  gainNode.gain.cancelScheduledValues(cancelAt);
  gainNode.gain.setTargetAtTime(0.00001, cancelAt, 0.1);
}
<input type="button" value="Start Oscillator" onclick="startSound();" />

<p>
  You can hear a JUMP in Gain (Volume) at around 1.5 seconds. That is when <strong>gainNode.gain.setTargetAtTime(0.00001, cancelAt, 0.1);</strong> is executed.
  It's is like it is executed Immedately instead at cancelAt (at second 2).
</p>

<p style="color: red; font-weight: bold;">
  NOTE: this would be expected if cancelAt <= audioCtx.currentTime. But that is NOT the case here. </p>

非常奇怪的是,如果我在预定的事件中添加一点时间,它似乎会按预期运行。见 cancelAt += 0.0005;

function startSound() {
  //Audio context
  var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  var currentTime = audioCtx.currentTime;

  //Create Gain Node And Oscillator
  var gainNode = audioCtx.createGain();
  var oscillator = audioCtx.createOscillator();
  oscillator.type = "sine";
  oscillator.frequency.setValueAtTime(440, currentTime);

  //Connect Oscillator to Gain Node and Gain Node to audioCtx.destination
  oscillator.connect(gainNode);
  gainNode.connect(audioCtx.destination);
  oscillator.start(currentTime);

  //Create Gain (Volume Envelope)
  gainNode.gain.setValueAtTime(0.5, currentTime);
  gainNode.gain.linearRampToValueAtTime(0.5, currentTime + 0.5);
  gainNode.gain.linearRampToValueAtTime(0.25, currentTime + 2);
  gainNode.gain.linearRampToValueAtTime(0.000001, currentTime + 5);
  gainNode.gain.linearRampToValueAtTime(0.000001, currentTime + 999);

  //Approximately 1.5 seconds later, Cancel ALL Scheduled Envelope changes from second 2 and up, and create a FAST
  //"Fade Out" that lasts 0.1 seconds.
  var cancelAt = currentTime + 2;

  setTimeout(function () {
    StopEnvelope(audioCtx, gainNode, cancelAt);
  }, 1500);
}

function StopEnvelope(audioCtx, gainNode, cancelAt) {
  cancelAt += 0.0005;
  
  //This is never TRUE
  if (cancelAt <= audioCtx.currentTime) {
    console.log("cancelScheduledValues will happen immediately!");
  }

  gainNode.gain.cancelScheduledValues(cancelAt);
  gainNode.gain.setTargetAtTime(0.00001, cancelAt, 0.1);
}
<input type="button" value="Start Oscillator" onclick="startSound();" />

<p>
Now the "Fade Out" works as expected. The only change is that 0.0005 seconds is added to the <strong>cancelAt</strong> paramenter.
</p>

<p style="color: red; font-weight: bold;">
  NOTE: As before, cancelAt is NEVER less than or equal to  audioCtx.currentTime. </p>

你知道这种奇怪行为的根源是什么吗?如果没有那个时间黑客可以解决这个问题吗?

非常感谢! 丹尼布洛

【问题讨论】:

    标签: javascript html web-audio-api


    【解决方案1】:

    问题是cancelScheduledValues() 将删除所有尚未在cancelTime 完成的自动化。在您的示例中,尽管第一个坡道实际上并没有做任何事情,但它包括所有坡道,因为它将信号从0.5 坡道到0.5。第二个斜坡被移除,因为它在第二个2 结束,因此在第二个2 尚未完成。这意味着,一旦您调用 cancelScheduledValues(),增益将立即恢复到 0.5,因为这是最后一个剩余动画的最终值。

    这也是为什么如果稍微增加cancelTime 似乎可以工作的原因。这样做时,第二个斜坡不会受到对cancelScheduledValues() 的调用的影响,并且效果不太听得见。

    您可以通过使用cancelAndHoldAtTime() 而不是cancelScheduledValues() 来实现所需的行为。它将保存最后一个值,下一个自动化将从那里开始。

    【讨论】:

    • 非常感谢您的快速回复。我试过了,它有效。但是有一个细节:假设有一个增益 RAMP 从:FROM Second 1 at value 1TO Second 2 at value 0.5..... At第二个 1.5 我打电话给cancelAndHoldAtTime(2)。现在增益不会跳跃,因为它保持当前读取的值在 1.5,尽管 RAMP 尚未在 1.5 完成。但这也意味着从 Second 1.5 到 2 的值将是 FLAT,而不是继续 RAMP,对吗?非常感谢!
    • 如果你打电话给cancelAndHoldAtTime(2),它应该在2秒取消自动化。如果currentTime 低于2,自动化应该继续并在第二个2 冻结。我通常使用github.com/hoch/canopy 来仔细检查那些边缘情况。
    • 是的。如此处所示:webaudio.github.io/web-audio-api/… 看起来像调用 cancelAndHoldAtTime(2) 虽然之前启动了一个 ACTIVE/IN-PROGRESS 自动化,但这样做:自动化一直持续到 Second 2 并从那时起,该值在该值处保持“平坦”那会在第二秒发生。再次,@chrisguttandin 非常感谢!!!!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-25
    • 1970-01-01
    • 2013-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多