Ga naar inhoud

Audio-visualizer

Hier gaan we dieper in op enkele van de audio-API-eigenschappen en laten we u de basis zien van het maken van audio-visualizers. De meeste visualizers zijn gebouwd op dezelfde set basisprincipes, dus een beetje oefening kan zeer leuke resultaten opleveren.

De audiogegevens die door SignalRGB worden geleverd, zijn toegankelijk via een paar eigenschappen in uw code:

  • engine.audio.level - geeft een getal terug tussen -100 en 0 dat de algemene luidheid van het nummer weergeeft. 0 is luid, -100 is zeer zacht.
  • engine.audio.density - geeft een getal terug tussen 0 en 1 dat de ruwheid van de toon weergeeft, waarbij testtonen 0 retourneren en witte ruis 1.
  • engine.audio.freq - geeft een array van 200 elementen terug met de frequentiegegevens van het nummer.

Elke eigenschap vereist verschillende niveaus van normalisatie of aanpassing voordat het correct kan worden gebruikt, wat ik straks zal bespreken.

Laten we beginnen met basisfrequentieanimatie.

Frequentie vertegenwoordigt de toonhoogte van het geluid dat we horen en is de belangrijkste eigenschap voor audio-visualizers. Wat we hier doen, is 200 stukjes van de frequentiegolf per frame nemen en deze omzetten naar visuele vorm. Het basisproces is:

  • Instantieer een array en vul deze met de frequentiegegevens.
  • Bewerk deze array naar uw wensen (filter, map, reduce, enz.).
  • Schrijf een “geluidsbalken”-klasse om elk element te vertegenwoordigen.
  • Verbind de gegevens elke frame met de geluidsbalkklasse.

Het belangrijkste bij frequentie is dat we twee aanpassingen moeten maken aan de ruwe gegevens. Soms komt een element binnen met een negatieve waarde, wat visueel storend is. De hoogte van de elementen komt ook onjuist binnen voor dit voorbeeld. Omdat positieve waarden in de optie “hoogte” van een rechthoek omlaag tekenen vanaf de oorsprong van de vorm, willen we deze omdraaien om op een gemiddelde visualizer te lijken.

Voorbeeld — onverwerkte gegevens:

Verwerkte gegevens:

Codevoorbeeld met verwerkte gegevens:

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

Er zijn echter nog enkele problemen met de bovenstaande visualizer. Hoewel het nummer door onze koptelefoon goed klinkt, kunnen we zien dat de gegevens enorm de voorkeur geven aan sommige geluidsbalken en andere vaak volledig onzichtbaar laten. Vanuit artistiek perspectief is dit niet ideaal, dus we gaan de gegevens die van SignalRGB worden ontvangen “normaliseren”. Normalisatie houdt in dat gegevens gelijkmatig worden verdeeld tussen een maximum en minimum punt om hun waarde ten opzichte van elkaar beter te illustreren. Na het uitzoeken van onze maximum- en minimumwaarden in de frequentiearray is de vergelijking vrij eenvoudig: (x - min) / (max - min), waarbij “x” het huidige element vertegenwoordigt.

Genormaliseerde, verwerkte gegevens:

Vervolgens voegen we een beetje flair toe door de geluidsbalken in een cirkel te rangschikken:

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

Dichtheid is eenvoudig te gebruiken. De geretourneerde waarde is een getal tussen 0 en 1 dat de “zuiverheid” van de toon weergeeft. Digitale tonen zijn dichter bij 0, en analoge tonen zijn dichter bij 1. Voor dit voorbeeld gebruik ik het om de kleur van de geluidsbalken te bewerken, wat ervoor zorgt dat verschillende tonen in uw nummer verschillende kleuren krijgen.

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

Deze eigenschap retourneert simpelweg de luidheid van het nummer in decibel, met als bijzonderheid dat het getallen produceert tussen -100 en 0. -100 is zeer stil en 0 is zeer luid. We moeten deze gegevens een beetje bewerken om ze te gebruiken zoals ik wil, en ik zal deze vorm tekenen in onze updatefunctie. Hier zal het trackniveau de helderheid van de binnenste zwarte cirkel bewerken.

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