1x
00:00
|
00:00
subscribe
share
more info
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:
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:
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:
context
con AudioContext
analyser
a partir del contexto con createAnalyser
new Audio
y le damos una fuente File o Blobsrc
de datos con createMediaElementSource
usando el nodo de audio
src
al analyser
context
con .destination
(esto hace que el audio se siga escuchando)fftSize
(un múltiplo de 8 siempre es mejor 😉)
Poquito rebuscado ¿verdad?
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.
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?
frequencyBinCount
que es la cantidad de datos disponible para trabajar (siempre es la mitad de fftSize
)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.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.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í
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
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.