使用 ConstantSourceNode 控制多个参数
本文演示了如何使用ConstantSourceNode
将多个参数链接在一起,以便它们共享相同的值,可以通过设置ConstantSourceNode.offset
参数的值来更改该值。
有时您可能希望将多个音频参数链接在一起,以便它们共享相同的值,同时以某种方式进行更改。例如,您可能有一组振荡器,其中两个需要共享相同的可配置音量,或者您对特定输入应用了一个滤波器,但并非对所有输入都应用。您可以使用循环并一次更改每个受影响的AudioParam
的值。但是,这样做有两个缺点:首先,这是额外的代码,正如您即将看到的,您不必编写这些代码;其次,该循环会占用线程(可能是主线程)宝贵的 CPU 时间,并且有一种方法可以将所有这些工作卸载到音频渲染线程,该线程针对此类工作进行了优化,并且可能以比您的代码更合适的优先级运行。
解决方案很简单,它涉及使用一种音频节点类型,乍一看似乎没什么用处:ConstantSourceNode
。
技术
使用ConstantSourceNode
是一种毫不费力的方法来完成一些听起来可能很困难的事情。您需要创建一个ConstantSourceNode
并将其连接到所有应始终匹配其值的AudioParam
。由于ConstantSourceNode
的offset
值会直接传递到其所有输出,因此它充当该值的拆分器,将其发送到每个连接的参数。
下图显示了它是如何工作的;输入值N
设置为ConstantSourceNode.offset
属性的值。ConstantSourceNode
可以具有任意数量的输出;在本例中,我们将其连接到三个节点:两个GainNode
和一个StereoPannerNode
。因此,N
成为指定参数的值(gain
对于GainNode
以及StereoPannerNode
的平移)。
因此,每次更改N
(输入AudioParam
的值时,两个GainNode.gain
属性的值和StereoPannerNode
的pan
属性的值也都会设置为N
。
示例
让我们看看这项技术的实际应用。在这个简单的示例中,我们创建了三个OscillatorNode
对象。其中两个具有可调节增益,使用共享的输入控件进行控制。另一个振荡器具有固定音量。
HTML
此示例的 HTML 内容主要是一个复选框(形状为实际按钮),用于切换振荡器音调的开/关,以及一个类型为range
的<input>
元素,用于控制三个振荡器中的两个的音量。
<div class="controls">
<input type="checkbox" id="playButton">
<label for="playButton">Activate: </label>
<label for="volumeControl">Volume: </label>
<input type="range" min="0.0" max="1.0" step="0.01"
value="0.8" name="volume" id="volumeControl">
</div>
</div>
<p>Toggle the checkbox above to start and stop the tones, and use the volume control to
change the volume of the notes E and G in the chord.</p>
JavaScript
现在让我们逐段查看 JavaScript 代码。
设置
让我们从查看全局变量初始化开始。
// Useful UI elements
const playButton = document.querySelector("#playButton");
const volumeControl = document.querySelector("#volumeControl");
// The audio context and the node will be initialized after the first request
let context = null;
let oscNode1 = null;
let oscNode2 = null;
let oscNode3 = null;
let constantNode = null;
let gainNode1 = null;
let gainNode2 = null;
let gainNode3 = null;
这些变量是
context
-
所有音频节点所在的
AudioContext
;它将在用户操作后初始化。 -
对播放按钮和音量控制元素的引用。
oscNode1
、oscNode2
和oscNode3
-
用于生成和弦的三个
OscillatorNode
。 gainNode1
、gainNode2
和gainNode3
-
三个
GainNode
实例,它们为三个振荡器中的每一个提供音量级别。gainNode2
和gainNode3
将链接在一起以具有相同(可调节)的值,使用ConstantSourceNode
。 constantNode
-
用于一起控制
gainNode2
和gainNode3
值的ConstantSourceNode
。
现在让我们看一下setup()
函数,该函数在用户第一次切换播放按钮时调用;它处理所有初始化任务以设置音频图。
function setup() {
context = new AudioContext();
gainNode1 = new GainNode(context, {
gain: 0.5,
});
gainNode2 = new GainNode(context, {
gain: gainNode1.gain.value,
});
gainNode3 = new GainNode(context, {
gain: gainNode1.gain.value,
});
volumeControl.value = gainNode1.gain.value;
constantNode = new ConstantSourceNode(context, {
offset: volumeControl.value,
});
constantNode.connect(gainNode2.gain);
constantNode.connect(gainNode3.gain);
constantNode.start();
gainNode1.connect(context.destination);
gainNode2.connect(context.destination);
gainNode3.connect(context.destination);
// All is set up. We can hook the volume control.
volumeControl.addEventListener("input", changeVolume, false);
}
首先,我们访问窗口的AudioContext
,并将引用存储在context
中。然后我们获取对控件部件的引用,将playButton
设置为引用播放按钮,并将volumeControl
设置为引用用户将用于调整链接的振荡器对的增益的滑块控件。
接下来,创建GainNode
gainNode1
来处理非链接振荡器(oscNode1
)的音量。我们将该增益设置为0.5。我们还创建gainNode2
和gainNode3
,将其值设置为与gainNode1
匹配,然后将音量滑块的值设置为相同的值,以便它与它控制的增益级别保持同步。
创建所有增益节点后,我们创建ConstantSourceNode
constantNode
。我们将它的输出连接到gainNode2
和gainNode3
上的gain
AudioParam
,并通过调用它的start()
方法启动常量节点的运行;现在它将值0.5发送到两个增益节点的值,并且对constantNode.offset
的任何更改都将自动设置gainNode2
和gainNode3
的增益(按预期影响其音频输入)。
最后,我们将所有增益节点连接到AudioContext
的destination
,以便传递到增益节点的任何声音都将到达输出,无论该输出是扬声器、耳机、录制流还是任何其他目标类型。
然后我们为音量滑块的input
事件分配一个处理程序(请参阅控制链接的振荡器以查看非常简短的changeVolume()
方法)。
在声明setup()
函数之后,我们向播放复选框的change
事件添加了一个处理程序(请参阅切换振荡器的开/关以了解有关togglePlay()
方法的更多信息),舞台已经搭建好了。让我们看看操作是如何进行的。
playButton.addEventListener("change", togglePlay, false);
切换振荡器的开/关
因为OscillatorNode
不支持暂停状态的概念,所以我们必须通过终止振荡器并在用户再次单击播放复选框以将其重新打开时重新启动它们来模拟它。让我们看看代码。
function togglePlay(event) {
if (!playButton.checked) {
stopOscillators();
} else {
// If it is the first start, initialize the audio graph
if (!context) {
setup();
}
startOscillators();
}
}
如果playButton
部件被选中,我们已经在播放振荡器,并且我们调用stopOscillators()
来关闭振荡器。请参阅下面停止振荡器以了解该代码。
如果playButton
部件被选中,表示我们当前处于暂停状态,我们调用startOscillators()
以开始播放振荡器的音调。在下面,我们在启动振荡器下描述了该代码。
控制链接的振荡器
changeVolume()
函数(链接的振荡器对增益的滑块控件的事件处理程序)如下所示
function changeVolume(event) {
constantNode.offset.value = volumeControl.value;
}
该简单函数控制两个节点的增益。我们只需设置ConstantSourceNode
的offset
参数的值即可。该值成为节点的常量输出值,馈送到其所有输出gainNode2
和gainNode3
。
虽然这是一个基本示例,但想象一下,如果有一个具有多个链接参数的 32 个振荡器合成器在许多修补节点中使用。减少调整它们所需的操作次数将被证明对代码大小和性能都非常有价值。
启动振荡器
当用户在振荡器未播放时单击播放/暂停切换按钮时,会调用startOscillators()
函数。
function startOscillators() {
oscNode1 = new OscillatorNode(context, {
type: "sine",
frequency: 261.625565300598634, // middle C$
});
oscNode1.connect(gainNode1);
oscNode2 = new OscillatorNode(context, {
type: "sine",
frequency: 329.627556912869929, // E
});
oscNode2.connect(gainNode2);
oscNode3 = new OscillatorNode(context, {
type: "sine",
frequency: 391.995435981749294, // G
});
oscNode3.connect(gainNode3);
oscNode1.start();
oscNode2.start();
oscNode3.start();
}
三个振荡器中的每一个都以相同的方式设置,通过使用两个选项调用OscillatorNode()
构造函数来创建OscillatorNode
- 将振荡器的
type
设置为"sine"
,以使用正弦波作为音频波形。 - 将振荡器的
frequency
设置为所需的值;在本例中,oscNode1
设置为中央C音,而oscNode2
和oscNode3
则通过演奏E和G音来补充和弦。
然后,我们将新的振荡器连接到相应的增益节点。
创建完所有三个振荡器后,通过依次调用每个振荡器的ConstantSourceNode.start()
方法来启动它们。
停止振荡器
当用户切换播放状态以暂停音调时,停止振荡器就像停止每个节点一样简单。
function stopOscillators() {
oscNode1.stop();
oscNode2.stop();
oscNode3.stop();
}
通过调用每个节点的ConstantSourceNode.stop()
方法来停止它。