如何使用 WebAudio API 模拟模块化合成的 VC 触发行为?

How can I mimic the VC trigger behavior of modular synthesis with the WebAudio API?

我有这张照片:http://bourt.com/color/slide.html。左键单击圆圈会发出咔嗒声。我希望在我松开鼠标按钮之前点击次数保持不变,并在我向上移动圆圈时使点击之间的间隔变小,直到点击形成一个音调。

我可以用简单的音调了解我需要的要点 (http://bourt.com/color/slide1.html),但我希望 LFO 触发我用来创建点击的包络。

在 VCV 中很容易做到,这是我制作原型的地方:

但我不太明白如何模仿使用低频振荡器触发包络的行为。据我了解,一种选择是使用 AnalyserNode.getFloatTimeDomainData(),找到峰值并相应地安排点击。另一种方法是完全放弃 LFO 概念,并使用 ScriptProcessor 作为临时混音器,通过手动将点击添加到缓冲区。但是对于概念上非常简单的东西来说,这是大量该死的工作。

所以我仍然希望有可能以某种方式使用振荡器来触发点击,并随着圆圈的位置改变振荡器的频率。是吗?

(我知道 The Tale Of Two Clocks,但我认为这种安排不适用于滑块的上部,因为点击的位置非常接近以至于会产生音调。)

使用逻辑电路(例如:LFO-based)确实会很痛苦。我建议您使用基于 setTimeout 的方法来注册未来的点击,同时仍然使用声音时钟作为触发播放的参考。我曾经在 JS 中构建过 audiotracker,这是最直接的解决方案。我自己的代码很乱,但我找到了一个很好的例子over here

在尝试 setTimeout() 调度方法后,很明显实现此目的的唯一方法是使用 ScriptProcessorNode 并自己将点击添加到音频缓冲区。和scheduling的方法差不多,但是所有的sample都得手动处理,很痛苦,不过我最终还是这么做了。

对于那些好奇的人,这是代码的样子(重要部分):

var clickProc       = null;
var clickBuffer     = null;

var clickSample     = 0;
var remSamples      = 0;

var remBuffer       = null;
var remBufferLength = 0;


remBuffer = new Float32Array(2048);
clickProc = ac.createScriptProcessor(2048, 1, 1);


clickProc.onaudioprocess = function (e)
{
    var output = e.outputBuffer.getChannelData(0);
    var click  = clickBuffer.getChannelData(0);


    for (var i = 0; i < output.length; i++)
        output[i] = 0;


    if (remBufferLength > 0)
    {
        for (i = 0; i < remBufferLength; i++)
            output[i] = remBuffer[i];

        remBufferLength = 0;
    }


    for (i = 0; i < remBuffer.length; i++)
        remBuffer[i] = 0;


    if (clickSample >= output.length)
        clickSample -= output.length;


    while (clickSample < output.length)
    {
        var c;

        for (c = 0; c < click.length && clickSample < output.length; c++, clickSample++)
            output[clickSample] = Math.min(Math.max(0, output[clickSample] + click[c]), 1);

        remSamples = click.length - c;

        clickSample -= c;
        clickSample += Math.max(1, Math.floor(clickInterval * ac.sampleRate));


        if (c < click.length)
        {
            var remLength = click.length - c;

            for (i = 0; i < remLength; i++, c++)
                remBuffer[i] = Math.min(Math.max(0, remBuffer[i] + click[c]), 1);

            remBufferLength = Math.max(remBufferLength, remLength);
        }
    }
};

clickProc.connect(ac.destination);