如何使用 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);
我有这张照片: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);