Héctor BlisS

@blissito

hace 2 años

Audio analyser y visualizador canvasEpisode 82·Season 4

Audio analyser y visualizador canvas

1x

00:00

|

00:00

subscribe

share

more info

Mira el video si prefieres:

Audio analyser y visualizador canvas

Hoy quiero practicar un poco de JavaScript y explorar mi curiosidad con las APIs del navegador para trabajar con las frecuencias de audio y combinarlas con canvas para crear visualizaciones, y si da tiempo de paso crear un custom hook con React para usarlas en cualquier momento.

Así que pongamos manos a la obra, y construyamos este proyecto en 3 partes:

  1. Entendamos el API
  2. Construyendo un visualizador con html + js
  3. Custom hook para usar el visualizador en React.

Entendiendo el API de AudioContext

La herramienta que vamos a usar para crear una visualización de audio en canvas es BaseAudioContext.createAnalyser() esta herramienta es parte del navegador recientemente y esta increíble herramienta puede ser usada para exponer el tiempo de un archivo de audio, su frecuencia y datos para poder crear visualizaciones. Esto es algo muy potente que no era fácil de obtener en los inicios de la web, así que entendamos la sintaxis y cómo se usa:

Analyser

1const context = new AudioContext() 2const analyser = audioCtx.createAnalyser(); 3

Como podemos ver, no es necesario pasar ningún parámetro a createAnalyser pero sí es necesario llamar este método desde una instancia del AudioContext

Una vez que tenemos el analizador, necesitamos ahora algo qué analizar es decir, una fuente, y la obtenemos desde un nodo de audio con createMediaElementSource:

1const context = new AudioContext() 2const analyser = audioCtx.createAnalyser(); 3const audio = new Audio() 4const src = context.createMediaElementSource(audio); 5

El analizador necesita analizar un audio, pero no podemos pasar el nodo de audio, necesitamos destriparlo y por ello createMediaElementSource crea una fuente de datos raw a partir de un nodo de audio.

Ojo. Necesitamos darle un File o un Blob a nuestro nodo de audio antes de entregárselo a createMediaElementSource

Tenemos todo lo que necesitamos, es momento de conectar. Nuestro set de herramientas completo queda algo así:

1const context = new AudioContext() // 1. 2const analyser = context.createAnalyser(); // 2. 3const audio = new Audio() 4audio.src = blob // 3. esto es más fácil si es un File (en el codepen puedes verlo mejor) 5const src = context.createMediaElementSource(audio); // 4. 6src.connect(analyser); // 5. 7analyser.connect(context.destination); // 6. 8analyser.fftSize = 256; // 7. Entero que representa el tamaño de la ventana de cuando se aplica el algoritmo de la "transformada de Fourier" un número alto resulta en mayor detalles en la frecuencia pero menos detalles en el tiempo. 9

Esas últimas 3 lineas pueden parecer confusas pero veamos en resumen qué es lo que hacemos:

  1. Creamos un contexto context con AudioContext
  2. Creamos un analizador analyser a partir del contexto con createAnalyser
  3. Creamos un nodo de audio con new Audio y le damos una fuente File o Blob
  4. Creamos una fuente src de datos con createMediaElementSource usando el nodo de audio
  5. Ahora conectamos todo. Primero conectamos la fuente src al analyser
  6. A su vez conectamos el analyser al context con .destination (esto hace que el audio se siga escuchando)
  7. Por último definimos la ventana FFT. fftSize (un múltiplo de 8 siempre es mejor 😉)

AudioContext Poquito rebuscado ¿verdad?

Creando la animación

Tooooodo eso, sólo para que el analizador contenga toda la data que necesitamos para dibujar. Es momento de exprimir a nuestro analyser que es la pieza más importante aquí.

Lo que haremos es una función que dibuje frame a frame el estado de la onda (de la frecuencia) para crear una animación. enter image description here

1const WIDTH = canvas.width; 2const HEIGHT = canvas.height; 3function renderFrame(analyser: AnalyserNode) { 4 const bufferLength = analyser.frequencyBinCount; // 1. 5 const dataArray = new Uint8Array(bufferLength); // 2. 6 const barWidth = (WIDTH / bufferLength) * 3; // 3. 7 let barHeight; 8 let x = 0; 9 requestAnimationFrame(() => renderFrame(analyser)); 10 analyser.getByteFrequencyData(dataArray); // 4. 11 ctx.fillStyle = "#111111"; 12 ctx.fillRect(0, 0, WIDTH, HEIGHT); // 5. 13 14 for (let i = 0; i < bufferLength; i++) { // 6. 15 barHeight = dataArray[i]; 16 const c = i / bufferLength; 17 const r = barHeight + 25 * c; 18 const g = 250 * c; 19 const b = 250; 20 ctx.fillStyle = `rgb(${r},${g},${b})`; 21 ctx.fillRect(x, HEIGHT - barHeight, barWidth, barHeight); 22 x += barWidth + 1; // 7. 23 } 24} 25

¿Qué pasa en esta función?

  1. Obtenemos frequencyBinCount que es la cantidad de datos disponible para trabajar (siempre es la mitad de fftSize)
  2. Los Uint8Array representan un array de enteros sin signo de 8 bits, estamos creando un array del tamaño del bufferLength para poder recorrerlo fácilmente.
  3. Conseguimos el ancho de las barras a partir del espacio disponible en el ancho del canvas (multiplico por 3 para hacer las barras más anchas, pero no es necesario)
  4. Lo que getByteFrequencyData hace, es guardar el dato actual de la frecuencia en el array, cada elemento del array representa ahora el valor del decibel de una frecuencia en específico.
  5. Aquí sólo estamos dibujando nuestro background.
  6. Aquí sucede la magia. Por cada elemento en el array usamos el dato del decibel como la altura de nuestra barra, calculamos un color semi-random. Y dibujamos la barra.

Tomate el tiempo de leer los puntos anteriores y leer el código.

Leyendo esto y esforzándote por entender qué pasa en cada etapa es como realmente vas a internalizar y aprender, y te permitirá modificar este código a tu gusto. Tomate el tiempo, hazme caso. 🤓

Checa el resultado Aquí

Hagamos un custom hook

Ahora sí viene lo bueno. Ya sabemos cómo usar nuestro analyser, y ya logramos usar los datos de las frecuencias para dibujar barras de diferentes alturas, ahora abstraigamos ese código para que otros developers puedan usarlo:

1import useAnalyser from "./useAnalyser"; 2 3export default function App() { 4const [player, bars] = useAnalyser("/chopin.mp3"); 5 6return ( 7 <div className="App"> 8 <h1>Audio Visualizer</h1> 9 {player} 10 <div className="container">{bars}</div> 11 </div> 12); 13} 14

Código del custom hook

Te dejo el código completo ¡PARA QUE LO LEAS!, recuerda que la lectura de código es lo que pone a nuestro cerebro en modo aprendizaje. Creo que entenderás cómo funciona de forma general, sólo quiero mencionarte algo importante:

Para que el analyser funcione necesitamos que el source del nodo de audio sea un archivo (file) o un blob, observa las línea 56 y 57 del snnipet:

1const blob = await fetch(source).then((r) => r.blob()); 2const url = URL.createObjectURL(blob); 3

Aquí es donde convierto el uri del mp3 a blob.

Bueno, eso es todo por hoy. Espero esto te ayude a seguir practicando y mejorando con JavaScript. Y si te gustó, dímelo en mi Twitter

Y si quieres comenzar con Canvas para entender mejor este ejercicio y saber por qué practicamos con canvas, comienza por Aquí

Abrazo. Bliss.

Suscríbete a mi lista VIP

Y no te pierdas las actualizaciones

No te enviaré spam. Desuscríbete en cualquier momento.