(Web Audio API)振荡器节点错误:无法多次调用启动

Jac*_*nkr 17 javascript javascript-oscillator web-audio-api

当我启动振荡器时,将其停止,然后重新启动; 我收到以下错误:

Uncaught InvalidStateError: Failed to execute 'start' on 'OscillatorNode': cannot call start more than once.
Run Code Online (Sandbox Code Playgroud)

显然我可以gain用来"停止"音频,但这让我感到很糟糕.什么是一种更有效的方法来停止振荡器,同时能够再次启动它?

代码(jsfiddle)

var ctx = new AudioContext();
var osc = ctx.createOscillator();

osc.frequency.value = 8000;

osc.connect(ctx.destination);

function startOsc(bool) {
    if(bool === undefined) bool = true;

    if(bool === true) {
        osc.start(ctx.currentTime);
    } else {
        osc.stop(ctx.currentTime);
    }
}

$(document).ready(function() {
    $("#start").click(function() {
       startOsc(); 
    });
    $("#stop").click(function() {
       startOsc(false); 
    });
});
Run Code Online (Sandbox Code Playgroud)

当前的解决方案(在提问时):http://jsfiddle.net/xbqbzgt2/2/

最终解决方案:http://jsfiddle.net/xbqbzgt2/3/

小智 25

更好的方法是启动一次振荡器节点,并在需要时连接/断开振荡器节点,即:

var ctx = new AudioContext();
var osc = ctx.createOscillator();   
osc.frequency.value = 8000;    
osc.start();    
$(document).ready(function() {
    $("#start").click(function() {
         osc.connect(ctx.destination);
    });
    $("#stop").click(function() {
         osc.disconnect(ctx.destination);
    });
});
Run Code Online (Sandbox Code Playgroud)

这是如何静音完成静音(mozilla web audio api文档)


Jac*_*nkr 8

到目前为止,我发现的最佳解决方案是audioContextoscillator每次需要使用它时重新创建SAME 。

http://jsfiddle.net/xbqbzgt2/3/

仅供参考,audioContext每个浏览器页面生命周期(或至少每个我的硬件)只能创建 6 个对象:

Uncaught NotSupportedError: Failed to construct 'AudioContext': The number of hardware contexts provided (6) is greater than or equal to the maximum bound (6).
Run Code Online (Sandbox Code Playgroud)

  • “每个浏览器页面生命周期只能创建 6 个 audioContext 对象”。就我而言,这是 4,但谢谢,这非常有帮助,并解决了我遇到的问题。 (2认同)

Mik*_*ans 8

虽然当前接受的答案有效,但有一种更好的方法可以做到这一点,基于振荡器不是“声源”,它们是信号源,并且“获得声音”的最佳方法不是仅在需要声音时启动(一个或多个)振荡器,但要让它们已经运行,并根据需要简单地允许它们的信号通过或阻止它们。

\n

因此,您真正想做的是对信号进行门控:如果您让它通过,并且它连接到音频输出,我们就会听到它,如果您阻止它,我们就听不到它。因此,即使您可能认为使用增益节点是“糟糕的做法”,但事实恰恰相反。我们绝对想使用增益节点:

\n
\n

信号 \xe2\x86\x92 音量控制 \xe2\x86\x92 音频输出

\n
\n

在这个链中,我们可以让信号永远运行(正如它应该的那样),并且我们可以使用音量控制来控制播放。例如,假设我们希望在单击按钮时播放 440Hz 的蜂鸣声。我们首先设置我们的链,一次:

\n
// the "audio output" in our chain:\nconst audioContext = new AudioContext();\n\n// the "volume control" in our chain:\nconst gainNode = audioContext.createGain();\ngainNode.connect(audioContext.destination);\ngainNode.gain.setValueAtTime(0, audioContext.currentTime);\n\n// the "signal" in our chain:\nconst osc = audioContext.createOscillator();\nosc.frequency.value = 440;\nosc.connect(gainNode);\nosc.start();\n
Run Code Online (Sandbox Code Playgroud)\n

然后为了播放蜂鸣声,我们使用setTargetAtTime函数将音量设置为 1,这让我们可以“在某个特定时间”更改参数,间隔(通常很短)间隔期间,值可以从“它的值”平滑地更改为“在某个特定时间”更改参数。是”到“我们想要的样子”。我们这样做是因为我们不希望我们在使用时出现那种噼啪声/爆裂声setValueAtTime:几乎可以保证在我们设置音量的那一刻信号不为零,因此扬声器必须跳到新的音量位置,产生那些可爱的裂缝。我们不想要那些。

\n

这也意味着我们没有构建任何新元素或生成器,没有分配或垃圾收集开销:我们只是设置控制最终到达音频目的地的信号类型的值:

\n
const smoothingInterval = 0.02;\nconst beepLengthInSeconds = 0.5;\n\nplayButton.addEventListener(`click`, () => {\n  const now = audioContext.currentTime;\n  gainNode.gain.setTargetAtTime(1, now, smoothingInterval);\n  gainNode.gain.setTargetAtTime(0, now + beepLengthInSeconds, smoothingInterval);\n});\n
Run Code Online (Sandbox Code Playgroud)\n

我们就完成了。振荡器始终运行,就像在实际的声音电路中一样,在运行时使用接近于零的资源,并且我们通过打开和关闭音量来控制是否可以听到它。

\n

当然,我们可以通过将链封装在具有其自身功能的东西中来使其更加有用:play()

\n

\r\n
\r\n
// the "audio output" in our chain:\nconst audioContext = new AudioContext();\n\n// the "volume control" in our chain:\nconst gainNode = audioContext.createGain();\ngainNode.connect(audioContext.destination);\ngainNode.gain.setValueAtTime(0, audioContext.currentTime);\n\n// the "signal" in our chain:\nconst osc = audioContext.createOscillator();\nosc.frequency.value = 440;\nosc.connect(gainNode);\nosc.start();\n
Run Code Online (Sandbox Code Playgroud)\r\n
const smoothingInterval = 0.02;\nconst beepLengthInSeconds = 0.5;\n\nplayButton.addEventListener(`click`, () => {\n  const now = audioContext.currentTime;\n  gainNode.gain.setTargetAtTime(1, now, smoothingInterval);\n  gainNode.gain.setTargetAtTime(0, now + beepLengthInSeconds, smoothingInterval);\n});\n
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n


小智 5

据我所知,振荡器只能播放一次,原因与精度有关,而且还没有人很好地解释过。无论谁决定采用这种“仅播放一次”模型,都可能会认为使用零音量设置在序列中间插入静音是一种很好的做法。毕竟,它确实是断开连接并重新创建方法的唯一替代方法。