Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Steuerung mehrerer Parameter mit ConstantSourceNode

Dieser Artikel zeigt, wie Sie einen ConstantSourceNode verwenden, um mehrere Parameter miteinander zu verknüpfen, so dass sie denselben Wert teilen, der geändert werden kann, indem Sie den Wert des Parameters ConstantSourceNode.offset setzen.

Manchmal möchten Sie vielleicht, dass mehrere Audioparameter so verknüpft sind, dass sie denselben Wert teilen, während sie irgendwie geändert werden. Beispielsweise haben Sie vielleicht ein Set von Oszillatoren, von denen zwei dasselbe konfigurierbare Volumen teilen müssen, oder Sie haben einen Filter, der auf bestimmte Eingänge angewendet wird, aber nicht auf alle. Sie könnten eine Schleife verwenden und den Wert jedes betroffenen AudioParam einzeln ändern. Es gibt jedoch zwei Nachteile bei dieser Vorgehensweise: Erstens ist das zusätzlicher Code, den Sie, wie Sie gleich sehen werden, nicht schreiben müssen; und zweitens verbraucht diese Schleife wertvolle CPU-Zeit in Ihrem Thread (wahrscheinlich im Hauptthread), und es gibt eine Möglichkeit, all diese Arbeit an den Audio-Rendering-Thread auszulagern, der für diese Art von Arbeit optimiert und möglicherweise auf einer angemesseneren Prioritätsstufe als Ihr Code ausgeführt wird.

Die Lösung ist einfach und beinhaltet die Verwendung eines Audio-Knotentyps, der auf den ersten Blick nicht allzu nützlich erscheint: ConstantSourceNode.

Die Technik

Die Verwendung eines ConstantSourceNode ist ein müheloser Weg, um etwas zu tun, das sich vielleicht schwierig anhört. Sie müssen einen ConstantSourceNode erstellen und ihn mit allen AudioParams verbinden, deren Werte so verknüpft sein sollen, dass sie immer übereinstimmen. Da der offset-Wert des ConstantSourceNode direkt an alle seine Ausgänge gesendet wird, fungiert er als Verteiler für diesen Wert und sendet ihn an jeden verbundenen Parameter.

Das untenstehende Diagramm zeigt, wie dies funktioniert; ein Eingabewert N wird als Wert der ConstantSourceNode.offset-Eigenschaft festgelegt. Der ConstantSourceNode kann so viele Ausgänge haben, wie nötig; in diesem Fall haben wir ihn mit drei Knoten verbunden: zwei GainNodes und einem StereoPannerNode. So wird N zum Wert des angegebenen Parameters (gain für die GainNodes und pan für den StereoPannerNode.

Diagramm im SVG-Format, das zeigt, wie der ConstantSourceNode verwendet werden kann, um einen Eingabeparameter auf mehrere Knoten zu verteilen.

Als Ergebnis wird jedes Mal, wenn Sie N ändern (den Wert des Eingabe-AudioParam), der Wert der beiden GainNode.gain-Eigenschaften und der Wert des StereoPannerNode-pan-Eigenschaften ebenfalls auf N gesetzt.

Beispiel

Schauen wir uns diese Technik in Aktion an. In diesem einfachen Beispiel erstellen wir drei OscillatorNode-Objekte. Zwei von ihnen haben einstellbares Gain, das über ein gemeinsames Eingabekontrollfeld gesteuert wird. Der andere Oszillator hat ein festes Volumen.

HTML

Der HTML-Inhalt für dieses Beispiel ist hauptsächlich ein Kontrollkästchen, das als echter Knopf gestaltet ist, um die Oszillator-Töne ein- und auszuschalten, sowie ein <input>-Element vom Typ range, um die Lautstärke von zwei der drei Oszillatoren zu steuern.

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

Sehen wir uns nun den JavaScript-Code an, Stück für Stück.

Einrichtung

Beginnen wir mit der Initialisierung der globalen Variablen.

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;

Diese Variablen sind:

context

Der AudioContext, in dem alle Audio-Knoten leben; er wird während einer Benutzeraktion initialisiert.

playButton und volumeControl

Referenzen auf die Play-Schaltfläche und das Lautstärkeregler-Element.

oscNode1, oscNode2 und oscNode3

Die drei OscillatorNodes, die den Akkord erzeugen.

gainNode1, gainNode2 und gainNode3

Die drei GainNode-Instanzen, die die Lautstärkepegel für jeden der drei Oszillatoren bereitstellen. gainNode2 und gainNode3 werden so verknüpft, dass sie denselben einstellbaren Wert mit einem ConstantSourceNode haben.

constantNode

Der ConstantSourceNode, der die Werte von gainNode2 und gainNode3 gemeinsam steuert.

Sehen wir uns nun die setup()-Funktion an, die beim ersten Umschalten der Play-Taste aufgerufen wird; sie behandelt alle Initialisierungsaufgaben, um den Audio-Graphen einzurichten.

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

Zuerst erhalten wir Zugriff auf den AudioContext des Fensters und speichern die Referenz in context. Dann holen wir Referenzen auf die Steuerungs-Widgets, indem wir playButton auf die Play-Taste und volumeControl auf das Schieberegler-Steuerelement verweisen, das der Benutzer verwenden wird, um das Gain des verknüpften Oszillator-Paares einzustellen.

Als nächstes wird der GainNode gainNode1 erstellt, um die Lautstärke des nicht verknüpften Oszillators (oscNode1) zu steuern. Wir setzen dieses Gain auf 0,5. Wir erstellen auch gainNode2 und gainNode3, setzen ihre Werte so, dass sie gainNode1 entsprechen, und setzen den Wert des Lautstärkereglers auf denselben Wert, sodass er synchron mit dem Gain-Pegel bleibt, den er steuert.

Sobald alle Gain-Knoten erstellt wurden, erstellen wir den ConstantSourceNode, constantNode. Wir verbinden seinen Ausgang mit dem gain-AudioParam bei gainNode2 und gainNode3, und wir starten den Konstantenknoten, indem wir seine start()-Methode aufrufen; nun sendet er den Wert 0,5 an die beiden Gain-Knoten, und jede Änderung von constantNode.offset wird automatisch das Gain von gainNode2 und gainNode3 einstellen (was erwartungsgemäß ihre Audioeingänge beeinflusst).

Schließlich verbinden wir alle Gain-Knoten mit dem destination des AudioContext, sodass jeder Ton, der an die Gain-Knoten geliefert wird, das Ausgabeziel erreicht, sei es Lautsprecher, Kopfhörer, ein Aufnahme-Stream oder ein anderer Ausgabetyp.

Dann weisen wir einen Handler für das input-Ereignis des Lautstärkereglers zu (siehe Steuerung der verknüpften Oszillatoren für die sehr kurze changeVolume()-Methode).

Direkt nach der Deklaration der setup()-Funktion fügen wir dem change-Ereignis des Play-Kontrollkästchens einen Handler hinzu (siehe Umschalten der Oszillatoren ein und aus für mehr zur togglePlay()-Methode), und die Bühne ist bereit. Sehen wir, wie die Aktion abläuft.

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

Umschalten der Oszillatoren ein und aus

Da OscillatorNode das Konzept eines Pausenzustandes nicht unterstützt, müssen wir es simulieren, indem wir die Oszillatoren beenden und sie erneut starten, wenn der Benutzer auf das Play-Kontrollkästchen klickt, um sie wieder einzuschalten. Werfen wir einen Blick auf den Code.

js
function togglePlay(event) {
  if (!playButton.checked) {
    stopOscillators();
  } else {
    // If it is the first start, initialize the audio graph
    if (!context) {
      setup();
    }
    startOscillators();
  }
}

Wenn das playButton-Widget aktiviert ist, spielen wir die Oszillatoren bereits, und wir rufen stopOscillators() auf, um die Oszillatoren auszuschalten. Den Code dazu finden Sie unter Stoppen der Oszillatoren weiter unten.

Wenn das playButton-Widget aktiviert ist, was darauf hinweist, dass wir derzeit pausiert sind, rufen wir startOscillators() auf, um die Oszillatoren ihre Töne spielen zu lassen. Weiter unten beschreiben wir diesen Code unter Starten der Oszillatoren.

Steuerung der verknüpften Oszillatoren

Die changeVolume()-Funktion, der Ereignis-Handler für das Schieberegler-Steuerelement für das Gain des verknüpften Oszillator-Paares, sieht so aus:

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

Diese einfache Funktion steuert das Gain auf beiden Knoten. Alles, was wir tun müssen, ist, den Wert des ConstantSourceNode's offset-Parameters einzustellen. Dieser Wert wird zum konstanten Ausgabewert des Knotens, der an all seine Ausgänge, gainNode2 und gainNode3, weitergegeben wird.

Während dies ein sehr einfaches Beispiel ist, stellen Sie sich vor, Sie haben einen Synthesizer mit 32 Oszillatoren und mehreren verknüpften Parametern, die über viele gepatchte Knoten hinweg in Betrieb sind. Die Anzahl der Operationen zu verkürzen, um sie alle anzupassen, wird sich als unschätzbar wertvoll erweisen, sowohl in Bezug auf den Codeumfang als auch auf die Leistung.

Starten der Oszillatoren

Wenn der Benutzer die Wiedergabe-/Pause-Taste klickt, während die Oszillatoren nicht spielen, wird die startOscillators()-Funktion aufgerufen.

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

Jeder der drei Oszillatoren wird auf die gleiche Weise eingerichtet, indem der OscillatorNode erstellt wird, indem Sie den OscillatorNode()-Konstruktor mit zwei Optionen aufrufen:

  1. Setzen Sie den Oszillator-type auf "sine", um eine Sinuswelle als Audio-Wellenform zu verwenden.
  2. Setzen Sie die Oszillator-frequency auf den gewünschten Wert; in diesem Fall wird oscNode1 auf ein mittleres C gesetzt, während oscNode2 und oscNode3 den Akkord abrunden, indem sie die E- und G-Noten spielen.

Dann verbinden wir den neuen Oszillator mit dem entsprechenden Gain-Knoten.

Nachdem alle drei Oszillatoren erstellt wurden, werden sie gestartet, indem wir jeweils die ConstantSourceNode.start()-Methode aufrufen.

Stoppen der Oszillatoren

Das Stoppen der Oszillatoren, wenn der Benutzer den Wiedergabestatus auf Pause umschaltet, ist so einfach wie das Stoppen jedes Knotens.

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

Jeder Knoten wird durch Aufrufen seiner ConstantSourceNode.stop()-Methode gestoppt.

Ergebnis

Siehe auch