Wizualizator audio
Tutaj zagłębiamy się w niektóre właściwości API audio i pokazujemy ci podstawy tworzenia wizualizatorów audio. Większość wizualizatorów jest zbudowana na tym samym zestawie podstawowych zasad, więc trochę praktyki może dać bardzo ciekawe rezultaty.
Właściwości audio SignalRGB
Dział zatytułowany „Właściwości audio SignalRGB”Dane audio dostarczane przez SignalRGB są dostępne za pomocą kilku właściwości w twoim kodzie:
- engine.audio.level - zwraca liczbę między -100 a 0 reprezentującą ogólną głośność utworu. 0 jest głośne, -100 jest bardzo ciche.
- engine.audio.density - zwraca liczbę między 0 a 1 reprezentującą chropowatość tonu, gdzie tony testowe zwracają 0, a biały szum zwraca 1.
- engine.audio.freq - zwraca tablicę 200 elementów z danymi częstotliwości utworu.
Każda właściwość wymaga różnych poziomów normalizacji lub dostosowania przed prawidłowym użyciem, co omówię za chwilę.
Zacznijmy od podstawowej animacji częstotliwości.
Częstotliwość
Dział zatytułowany „Częstotliwość”Częstotliwość reprezentuje wysokość dźwięku, który słyszymy i jest najważniejszą właściwością dla wizualizatorów audio. To, co tutaj robimy, to pobieranie 200 fragmentów fali częstotliwości na klatkę i przekształcanie ich w formę wizualną. Podstawowy proces to:
- Utwórz tablicę i wypełnij ją danymi częstotliwości.
- Edytuj tę tablicę według swoich potrzeb (filtrowanie, mapowanie, redukcja itp.).
- Napisz klasę “słupków dźwiękowych”, aby reprezentować każdy element.
- Połącz dane z klasą słupków dźwiękowych w każdej klatce.
Najważniejszą rzeczą przy częstotliwości jest to, że musimy dokonać dwóch korekt surowych danych. Czasami element przychodzi z wartością ujemną, co jest wizualnie zakłócające. Wysokość elementów również przychodzi nieprawidłowo dla tego przykładu. Ponieważ wartości dodatnie w opcji “wysokość” prostokąta rysują w dół od punktu początkowego kształtu, chcemy je odwrócić, aby wyglądały jak typowy wizualizator.
Przykład — surowe dane:

Przetworzone dane:

Przykład kodu z przetworzonymi danymi:
<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>Jednak powyższy wizualizator ma jeszcze kilka problemów. Chociaż utwór brzmi dobrze w naszych słuchawkach, możemy zobaczyć, że dane znacznie faworyzują niektóre słupki dźwiękowe, a inne często pozostają całkowicie niewidoczne. Z artystycznego punktu widzenia nie jest to idealne, więc zamierzamy “znormalizować” dane otrzymywane od SignalRGB. Normalizacja polega na równomiernym rozłożeniu danych między punktem maksymalnym i minimalnym, aby lepiej zilustrować ich wartość względem siebie. Po ustaleniu wartości maksymalnej i minimalnej w tablicy częstotliwości, równanie jest dość proste: (x - min) / (max - min), gdzie “x” reprezentuje bieżący element.
Znormalizowane, przetworzone dane:

Następnie dodajemy trochę stylu, układając słupki dźwiękowe w okrąg:
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() } } }
Gęstość
Dział zatytułowany „Gęstość”Gęstość jest prosta w użyciu. Zwracana wartość to liczba między 0 a 1 reprezentująca “czystość” tonu. Dźwięki cyfrowe są bliżej 0, a analogowe bliżej 1. W tym przykładzie używam jej do edycji koloru słupków dźwiękowych, co sprawia, że różne tony w twoim utworze otrzymują różne kolory.
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() } } }
Ta właściwość po prostu zwraca głośność utworu w decybelach, z tą szczególną cechą, że produkuje liczby między -100 a 0. -100 to bardzo cicho, a 0 to bardzo głośno. Musimy trochę zmodyfikować te dane, aby użyć ich w taki sposób, jaki chcę, i narysuję ten kształt w naszej funkcji aktualizacji. Tutaj poziom ścieżki będzie edytować jasność wewnętrznego czarnego okręgu.
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); }