使用 ConstantSourceNode 控制多個引數

本文演示瞭如何使用 ConstantSourceNode 將多個引數連結在一起,使它們共享相同的值,該值可以透過設定 ConstantSourceNode.offset 引數的值來更改。

有時您可能希望將多個音訊引數連結在一起,使它們在以某種方式更改時共享相同的值。例如,您可能有一組振盪器,其中兩個需要共享相同的可配置音量,或者您有一個應用於特定輸入但並非所有輸入的濾波器。您可以逐個更改每個受影響 AudioParam 的值。儘管如此,這樣做有兩個缺點:首先,這是額外的程式碼,正如您即將看到的,您不必編寫這些程式碼;其次,該迴圈會佔用您執行緒(可能是主執行緒)上的寶貴 CPU 時間,並且有一種方法可以將所有這些工作解除安裝到音訊渲染執行緒,該執行緒經過最佳化,可以處理此類工作,並且可能比您的程式碼具有更高的優先順序。

解決方案很簡單,它涉及使用一種看似用處不大的音訊節點型別:ConstantSourceNode

技術

使用 ConstantSourceNode 是一種輕鬆完成聽起來可能很難的事情的方法。您需要建立一個 ConstantSourceNode 並將其連線到所有應連結其值以始終匹配的 AudioParam。由於 ConstantSourceNodeoffset 值直接傳送到其所有輸出,因此它充當該值的分配器,將其傳送到每個連線的引數。

下圖顯示了此工作原理;輸入值 N 被設定為 ConstantSourceNode.offset 屬性的值。ConstantSourceNode 可以擁有任意數量的輸出;在這種情況下,我們已將其連線到三個節點:兩個 GainNode 和一個 StereoPannerNode。因此,N 成為指定引數的值(對於 GainNode,它是 gain,對於 StereoPannerNode,它是 pan)。

Diagram in SVG showing how ConstantSourceNode can be used to split an input parameter to share it with multiple nodes.

結果是,每次更改 N(輸入 AudioParam 的值)時,兩個 GainNode.gain 屬性的值以及 StereoPannerNodepan 屬性的值都設定為 N

示例

讓我們來看看這項技術的實際應用。在這個簡單的例子中,我們建立了三個 OscillatorNode 物件。其中兩個具有可調增益,透過共享輸入控制元件進行控制。另一個振盪器具有固定的音量。

HTML

此示例的 HTML 內容主要是一個複選框,它被塑造成一個實際的按鈕,用於切換振盪器音調的開/關,以及一個型別為 range<input> 元素,用於控制三個振盪器中兩個的音量。

html
<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>

<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 程式碼。

設定

讓我們先從全域性變數初始化開始。

js
// 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;它將在使用者操作後初始化。

playButtonvolumeControl

對播放按鈕和音量控制元件元素的引用。

oscNode1oscNode2oscNode3

用於生成和絃的三個 OscillatorNode

gainNode1gainNode2gainNode3

提供三個振盪器各自音量的三個 GainNode 例項。gainNode2gainNode3 將使用 ConstantSourceNode 連結在一起,以具有相同的、可調節的值。

constantNode

用於一起控制 gainNode2gainNode3 值的 ConstantSourceNode

現在讓我們看看 setup() 函式,它在使用者第一次切換播放按鈕時呼叫;它負責設定音訊圖的所有初始化任務。

js
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);
}

首先,我們獲取對視窗 AudioContext 的訪問,將引用儲存在 context 中。然後,我們獲取對控制元件小部件的引用,將 playButton 設定為引用播放按鈕,將 volumeControl 設定為引用使用者將用於調整連結振盪器對增益的滑塊控制元件。

接下來,建立 GainNode gainNode1 來處理非連結振盪器 (oscNode1) 的音量。我們將該增益設定為 0.5。我們還建立 gainNode2gainNode3,將它們的值設定為與 gainNode1 匹配,然後將音量滑塊的值設定為相同的值,以便它與它控制的增益級別保持同步。

建立完所有增益節點後,我們建立 ConstantSourceNode constantNode。我們將其輸出連線到 gainNode2gainNode3 上的 gain AudioParam,並透過呼叫其 start() 方法來執行常量節點;現在它正在將值 0.5 傳送到兩個增益節點的兩個值,並且對 constantNode.offset 的任何更改都將自動設定 gainNode2gainNode3 的增益(按預期影響它們的音訊輸入)。

最後,我們將所有增益節點連線到 AudioContextdestination,以便任何傳送到增益節點的音訊都能到達輸出,無論該輸出是揚聲器、耳機、錄音流還是任何其他型別的目的地。

然後,我們為音量滑塊的 input 事件分配一個處理程式(請參閱 控制連結的振盪器 檢視非常簡短的 changeVolume() 方法)。

在宣告 setup() 函式後,我們向播放複選框的 change 事件新增一個處理程式(有關 togglePlay() 方法的更多資訊,請參閱 切換振盪器的開/關),一切準備就緒。讓我們看看它是如何執行的。

js
playButton.addEventListener("change", togglePlay);

切換振盪器的開/關

由於 OscillatorNode 不支援暫停狀態的概念,因此我們必須透過終止振盪器並在使用者再次單擊播放複選框以重新開啟它們時再次啟動它們來模擬它。讓我們看看程式碼。

js
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() 函式,即連結振盪器對增益的滑塊控制元件的事件處理程式,如下所示:

js
function changeVolume(event) {
  constantNode.offset.value = volumeControl.value;
}

該簡單函式控制兩個節點的增益。我們所要做的就是設定 ConstantSourceNodeoffset 引數的值。該值成為節點的常量輸出值,饋送到其所有輸出 gainNode2gainNode3

雖然這是一個基礎示例,但試想一下一個擁有 32 個振盪器的合成器,其中有多個連結引數在許多已連線的節點中發揮作用。減少調整所有引數的操作次數將對程式碼大小和效能都大有裨益。

啟動振盪器

當用戶在振盪器未播放時單擊播放/暫停切換按鈕時,會呼叫 startOscillators() 函式。

js
function startOscillators() {
  oscNode1 = new OscillatorNode(context, {
    type: "sine",
    frequency: 261.6255653005986, // middle C$
  });
  oscNode1.connect(gainNode1);

  oscNode2 = new OscillatorNode(context, {
    type: "sine",
    frequency: 329.6275569128699, // E
  });
  oscNode2.connect(gainNode2);

  oscNode3 = new OscillatorNode(context, {
    type: "sine",
    frequency: 391.99543598174927, // G
  });
  oscNode3.connect(gainNode3);

  oscNode1.start();
  oscNode2.start();
  oscNode3.start();
}

三個振盪器中的每一個都以相同的方式設定,透過呼叫 OscillatorNode() 建構函式並帶有兩個選項來建立 OscillatorNode

  1. 將振盪器的 type 設定為 "sine" 以使用正弦波作為音訊波形。
  2. 將振盪器的 frequency 設定為所需值;在此示例中,oscNode1 設定為中央 C,而 oscNode2oscNode3 透過播放 E 和 G 音符來完成和絃。

然後,我們將新的振盪器連線到相應的增益節點。

一旦所有三個振盪器都被建立,它們就會透過依次呼叫每個振盪器的 ConstantSourceNode.start() 方法來啟動。

停止振盪器

當用戶切換播放狀態以暫停音調時停止振盪器,這與停止每個節點一樣簡單。

js
function stopOscillators() {
  oscNode1.stop();
  oscNode2.stop();
  oscNode3.stop();
}

透過呼叫每個節點的 ConstantSourceNode.stop() 方法來停止每個節點。

結果

另見