Audio-Visualizer
Hier tauchen wir in einige der API-Audio-Eigenschaften ein und zeigen dir die Grundlagen der Audio-Visualizer-Erstellung. Die meisten Visualizer basieren auf denselben grundlegenden Prinzipien, sodass etwas Übung sehr unterhaltsame Ergebnisse liefern kann.
SignalRGB Audio-Eigenschaften
Abschnitt betitelt „SignalRGB Audio-Eigenschaften“Die von SignalRGB bereitgestellten Audiodaten können über einige Eigenschaften in deinem Code abgerufen werden:
- engine.audio.level - gibt eine Zahl zwischen -100 und 0 zurück, die die Gesamtlautstärke des Tracks angibt. 0 ist laut, -100 ist sehr leise.
- engine.audio.density - gibt eine Zahl zwischen 0 und 1 zurück, die die Rauheit des Tons angibt; Testtöne geben 0 zurück, weißes Rauschen 1.
- engine.audio.freq - gibt ein Array mit 200 Elementen zurück, das die Frequenzdaten des Tracks enthält.
Jede Eigenschaft erfordert unterschiedliche Normalisierungs- oder Anpassungsschritte, bevor sie richtig genutzt werden kann, auf die ich gleich eingehen werde.
Beginnen wir mit der grundlegenden Frequenzanimation.
Frequency
Abschnitt betitelt „Frequency“Frequency (Frequenz) repräsentiert die Tonhöhe des gehörten Klangs und ist die wichtigste Eigenschaft für Audio-Visualizer. Hier nehmen wir pro Frame 200 Scheiben der Frequenzwelle und wandeln sie in eine visuelle Form um. Der grundlegende Ablauf ist:
- Ein Array instanziieren und mit den Frequenzdaten füllen.
- Dieses Array nach Bedarf bearbeiten (filtern, mappen, reduzieren usw.).
- Eine “Soundbar”-Klasse schreiben, um jedes Element darzustellen.
- Die Daten pro Frame mit der Soundbar-Klasse verbinden.
Das Wichtige bei der Frequenz ist, dass wir zwei Anpassungen an den Rohdaten vornehmen müssen. Manchmal kommt ein Element mit einem negativen Wert an, was visuell störend ist. Die Höhe der Elemente kommt für dieses Beispiel ebenfalls falsch an. Da positive Werte in der “height”-Option eines Rechtecks vom Ursprung der Form nach unten gezeichnet werden, wollen wir diese umkehren, damit es einem durchschnittlichen Visualizer ähnelt.
Beispiel - unverarbeitete Daten:

Verarbeitete Daten:

Code-Beispiel mit verarbeiteten Daten:
<head> <title>Visualizer Tutorial</title> <meta description="Basic Effects" /> <meta publisher="SignalRgb" /> </head>
<body style="margin: 0; padding: 0; background: #000;"> <canvas id="exCanvas" width="320" height="200"></canvas> </body>
<script> canvas = document.getElementById('exCanvas'); ctx = canvas.getContext('2d'); var effects = []; var reducedFreq = [];
function update() {
// "frequency" represents the full 200 elements from our frequency data var frequency = new Int8Array(engine.audio.freq) // "reducedFreq" filters the data down to every fourth result to save some CPU load reducedFreq = frequency.filter((element, index) => { return index % 4 === 0; })
// Create the effect if it does not yet exist if(effects.length < 1){ effects.push(new soundBars(20, 100)) }
// Background color DrawRect(0, 0, 320, 200, "black")
// Play effect effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime <= 0) { effects.splice(i, 1); } });
window.requestAnimationFrame(update); }
function soundBars (x, y){ this.x = x; this.y = y; this.draw = function(){ for(let i = 0; i < reducedFreq.length; i++){ var x = this.x + 5 * i var y = this.y // Data processing occurs for "height". Find the absolute value of each element, then flip negative var height = -Math.abs(reducedFreq[i]) DrawRect(x, y, 5, height, "white") } } }
function DrawRect(x, y, width, height, color) { ctx.beginPath(); ctx.fillStyle = color; ctx.fillRect(x, y, width, height); };
window.requestAnimationFrame(update); </script>Es gibt jedoch noch einige Probleme mit dem obigen Visualizer. Obwohl das Lied über unsere Kopfhörer ausgewogen klingt, sehen wir, dass die Daten einige unserer Soundbars massiv bevorzugen und andere oft vollständig unsichtbar lassen. Aus künstlerischer Sicht ist das nicht ideal, daher werden wir die von SignalRGB empfangenen Daten “normalisieren”. Bei der Normalisierung werden Daten gleichmäßig zwischen einem Maximal- und Minimalwert verteilt, um ihren Wert im Verhältnis zueinander besser darzustellen. Nachdem wir unsere Maximal- und Minimalwerte im Frequenz-Array herausgefunden haben, ist die Gleichung insgesamt recht einfach: (x - min) / (max - min), wobei “x” das aktuelle Element darstellt.
Normalisierte, verarbeitete Daten:

Als Nächstes fügen wir etwas Schwung hinzu, indem wir die Soundbars in einem Kreis anordnen:
function soundBars (x, y){ this.x = x; this.y = y;
this.draw = function(){
var max = Math.max(...reducedFreq) var min = Math.min(...reducedFreq)
for(let i = 0; i < reducedFreq.length; i++){ //Save the current state of the canvas ctx.save() //Add the circular component to your bars. "50" here is the radius of the circle var x = this.x + Math.cos(i) * 50 var y = this.y + Math.sin(i) * 50 var height = ((Math.abs(reducedFreq[i])) - min) / (max - min) * -40 // Determine the angle of the bar, to make it perpindicular to the circle var rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2; //Translate to circle center ctx.translate(x, y) //Rotate the canvas ctx.rotate(rotate) //Translate back for drawing ctx.translate(-x, -y) DrawRect(x, y, 5, height, "white") //Restore the old state of the canvas after drawing to prevent positive feedback loops ctx.restore() } } }
Density
Abschnitt betitelt „Density“Density (Dichte) ist einfach zu verwenden. Der zurückgegebene Wert ist eine Zahl zwischen 0 und 1, die die “Sauberkeit” des Tons darstellt. Digitale Töne liegen näher bei 0, analoge Töne näher bei 1. In diesem Beispiel verwende ich sie, um die Farbe der Soundbars zu bearbeiten, was bestimmten Tönen in deinem Song bestimmte Farbgebungen verleiht.
function soundBars (x, y){
this.x = x; this.y = y; this.rotate = 0;
this.draw = function(){ //Hue is calculated each frame, the resulting amount will be a proper hue option for hsl var hue = engine.audio.density * 360
var max = Math.max(...reducedFreq) var min = Math.min(...reducedFreq) for(let i = 0; i < reducedFreq.length; i++){ ctx.save() var x = this.x + Math.cos(i) * 50 var y = this.y + Math.sin(i) * 50 //Height slightly edited for visability var height = ((Math.abs(reducedFreq[i])) - min) / (max - min) * -50 - 5 this.rotate = Math.atan2(y - this.y, x - this.x) + Math.PI / 2; ctx.translate(x, y) ctx.rotate(this.rotate) ctx.translate(-x, -y) //Insert the edited hue value each frame DrawRect(x, y, 5, height, `hsl(${hue}, 100%, 50%)`) this.rotate = 0; ctx.restore() } } }
Diese Eigenschaft gibt einfach die Lautstärke des Tracks in Dezibel zurück, wobei der Trick darin besteht, dass sie Zahlen zwischen -100 und 0 erzeugt. -100 ist sehr leise und 0 ist sehr laut. Wir müssen diese Daten etwas bearbeiten, um sie wie gewünscht zu verwenden, und ich zeichne diese Form in unserer update-Funktion. Hier bearbeitet der Track-Level die Helligkeit des inneren schwarzen Kreises.
function update() {
var frequency = new Int8Array(engine.audio.freq) reducedFreq = frequency.filter((element, index) => { return index % 4 === 0; })
if(effects.length < 1){ effects.push(new soundBars(160, 100)) }
DrawRect(0, 0, 320, 200, "black")
effects.forEach((ele, i) => { ele.draw(); if (ele.lifetime <= 0) { effects.splice(i, 1); } });
// Hue calcualtion to match the rest of the visualizer var hue = engine.audio.density * 360 //The level has been multiplied by 10, then added to 100. This is arbitrary, but some combination of actions here will give you the result you want DrawCircle(160, 100, 50, `hsl(${hue}, 100%, ${100 + engine.audio.level * 10}%)`)
window.requestAnimationFrame(update); }