Juego de audio simplificado

La Web Audio API es un poderoso aliado para cualquiera que cree juegos de JavaScript, pero con ese poder viene la complejidad. Web Audio es un sistema modular; Los nodos de audio se pueden conectar entre sí para formar gráficos complejos para manejar todo, desde la reproducción de un solo sonido hasta una aplicación de secuenciación de música con todas las funciones. Esto es impresionante, por decir lo menos..

Sin embargo, cuando se trata de juegos de programación, la mayoría de los desarrolladores desean una API básica que simplemente cargue y reproduzca sonidos, y brinda opciones para cambiar el volumen, el tono y el panorama (posición estéreo) de esos sonidos. Este tutorial proporciona una solución elegante envolviendo la Web Audio API en un formato rápido y ligero Sonar clase que maneja todo por ti.

Nota: Este tutorial está dirigido principalmente a programadores de JavaScript, pero las técnicas utilizadas para mezclar y manipular el audio en el código se pueden aplicar a casi cualquier entorno de programación que proporcione acceso a una API de sonido de bajo nivel..

Demo en vivo

Antes de comenzar, eche un vistazo a la demostración en vivo de Sonar Clase en acción. Puede hacer clic en los botones en la demostración para reproducir sonidos:

  • SFX 01 Es un sonido de un solo disparo con la configuración predeterminada.. 
  • SFX 02 es un sonido de un solo disparo que tiene su panorama (posición estéreo) y volumen aleatorio cada vez que se reproduce. 
  • SFX 03 es un sonido en bucle; al hacer clic en el botón se activará y desactivará el sonido, y la posición del puntero del ratón dentro del botón ajustará el tono del sonido.

Nota: Si no escucha ningún sonido que se está reproduciendo, el navegador web que está utilizando no es compatible con la Web Audio API o con las secuencias de audio OGG Vorbis. Usar Chrome o Firefox debería resolver el problema.

La vida puede ser más simple

La siguiente imagen visualiza un gráfico de nodo de Web Audio básico:

Ejemplo visual de un gráfico de nodo de audio web.

Como puede ver, hay bastantes nodos de audio en el gráfico para manejar la reproducción de cuatro sonidos de una manera adecuada para los juegos. Los nodos panorámicos y de ganancia se ocupan de la panoramización y el volumen, y hay un par de nodos de compresores dinámicos allí para ayudar a evitar artefactos audibles (clips, pops, etc.) si el gráfico termina sobrecargado por un sonido alto..

Poder crear gráficos de nodos de audio como los de JavaScript es increíble, pero tener que crear, conectar y desconectar constantemente esos nodos puede convertirse en una verdadera carga. Vamos a simplificar las cosas mediante el manejo de la mezcla y la manipulación de audio mediante un solo nodo de procesador de scripts..

Ejemplo visual de un gráfico de nodo de Web Audio simplificado.

Sí, eso es definitivamente mucho más simple, y también evita la sobrecarga de procesamiento involucrada en la creación, conexión y desconexión de una carga de nodos de audio cada vez que se necesita reproducir un sonido.. 

Hay otras peculiaridades en la Web Audio API que pueden dificultar las cosas. El nodo panorámico, por ejemplo, está diseñado específicamente para los sonidos que están posicionados en el espacio 3D, no en el espacio 2D, y los nodos de fuente de búfer de audio (etiquetados como "sonido" en la imagen anterior) solo se pueden reproducir una vez, por lo tanto, la necesidad de crear constantemente y conectar esos tipos de nodos.

El nodo de procesador de script único que utiliza el Sonar la clase solicita periódicamente que se le pasen muestras de sonido desde JavaScript, y eso nos facilita mucho las cosas. Podemos mezclar y manipular muestras de sonido de forma muy rápida y sencilla en JavaScript, para producir las funciones de volumen, tono y panorama que necesitamos para los juegos 2D..

La clase de sonido

En lugar de caminar a través de la creación de la Sonar En esta clase, analizaremos las partes centrales del código que están directamente relacionadas con la API de audio web y la manipulación de muestras de sonido. Los archivos fuente de demostración incluyen el totalmente funcional Sonar Clase, que puedes estudiar y usar libremente en tus propios proyectos..

Cargando archivos de sonido

los Sonar la clase carga archivos de sonido a través de una red como buffers de matriz usando XMLHttpRequest objetos. Los búferes de matriz se decodifican en muestras de sonido sin procesar por un objeto de contexto de audio.

request.open ("GET", "sound.ogg"); request.onload = decode; request.responseType = "arraybuffer"; request.open (); function decode () if (request.response! == null) audioContext.decodeAudioData (request.response, done);  función realizada (audioBuffer) …

Obviamente, no hay un manejo de errores en ese código, pero demuestra cómo se cargan y descodifican los archivos de sonido. los AudioBuffer pasado a la hecho() La función contiene las muestras de sonido en bruto del archivo de sonido cargado..

Mezclando y manipulando muestras de sonido

Para mezclar y manipular las muestras de sonido cargadas, el Sonar la clase adjunta un oyente a un nodo procesador de scripts. Este oyente será llamado periódicamente para solicitar más muestras de sonido..

// Calcular un tamaño de búfer. // Esto producirá un valor sensible que equilibra la latencia del audio y el uso de la CPU para juegos que se ejecutan a 60 Hz. var v = audioContext.sampleRate / 60; var n = 0; while (v> 0) v >> = 1; n ++;  v = Math.pow (2, n); // tamaño del búfer // Crear el procesador de scripts. procesador = audioContext.createScriptProcessor (v); // Adjuntar el oyente. processor.onaudioprocess = processSamples; función processSamples (evento) …

La frecuencia con la que el procesoMuestras () se llama a la función variará en diferentes configuraciones de hardware, pero por lo general es alrededor de 45 veces por segundo. Puede parecer mucho, pero se requiere mantener la latencia del audio lo suficientemente baja como para poder usarla en juegos modernos que normalmente se ejecutan a 60 cuadros por segundo. Si la latencia del audio es demasiado alta, los sonidos se escucharán demasiado tarde para sincronizarse con lo que está sucediendo en la pantalla, y eso sería una experiencia discordante para cualquiera que juegue un juego..

A pesar de la frecuencia con la que el procesoMuestras () Se llama a la función, el uso de la CPU sigue siendo bajo, así que no se preocupe si se le quita demasiado tiempo a la lógica y la representación del juego. En mi hardware (Intel Core i3, 3 GHz) el uso de la CPU rara vez supera el 2%, incluso cuando se reproducen muchos sonidos simultáneamente.

los procesoMuestras () En realidad, la función contiene la carne de la Sonar clase; es donde las muestras de sonido se mezclan y manipulan antes de ser enviadas a través del audio web al hardware. El siguiente código demuestra lo que sucede dentro de la función:

// Agarra la muestra de sonido. sampleL = samplesL [soundPosition >> 0]; sampleR = samplesR [soundPosition >> 0]; // Aumentar la posición del cabezal de reproducción del sonido. soundPosition + = soundScale; // Aplicar el volumen global (afecta a todos los sonidos). sampleL * = globalVolume; sampleR * = globalVolume; // Aplicar el volumen del sonido. sampleL * = soundVolume; sampleR * = sonidoVolumen; // Aplicar la panoramización del sonido (posición estéreo). sampleL * = 1.0 - soundPan; sampleR * = 1.0 + soundPan;

Eso es más o menos todo lo que hay que hacer. Esa es la magia: un puñado de operaciones simples cambian el volumen, el tono y la posición estéreo de un sonido.

Si usted es un programador y está familiarizado con este tipo de procesamiento de sonido, puede estar pensando que "eso no puede ser todo lo que hay", y sería correcto: la clase de Sonido debe realizar un seguimiento de las instancias de sonido, los búferes de muestra y hacer algunas otras cosas, pero eso es todo corriente!

Usando la clase de sonido

El siguiente código muestra cómo usar la clase de sonido. También puede descargar los archivos de origen para la demostración en vivo que acompaña a este tutorial..

// Crea un par de objetos de sonido. var boom = nuevo sonido ("boom.ogg"); var tick = nuevo sonido ("tick.ogg"); // Opcionalmente pase un oyente a la clase de sonido. Sound.setListener (oyente); // Esto cargará cualquier objeto de sonido creado recientemente. Sound.load (); // El oyente. función de escucha (sonido, estado) si (estado === Sound.State.LOADED) si (sonido === marca) setInterval (playTick, 1000);  else if (sonido === boom) setInterval (playBoom, 4000);  else else (estado === Sound.State.ERROR) console.warn ("Error de sonido:% s", sound.getPath ());  // Reproduce el sonido de tic. función playTick () tick.play ();  // Reproduce el sonido boom. función playBoom ​​() boom.play (); // Aleatorizar el tono y el volumen del sonido. boom.setScale (0.8 + 0.4 * Math.random ()); boom.setVolume (0.2 + 0.8 * Math.random ()); 

Bonito y fácil.

Una cosa a tener en cuenta: no importa si la Web Audio API no está disponible en un navegador, y no importa si el navegador no puede reproducir un formato de sonido específico. Todavía puedes llamar al jugar() y detener() funciona en un Sonar Objeto sin ningún error de ser lanzado. Eso es intencional; le permite ejecutar su código de juego como de costumbre, sin preocuparse por los problemas de compatibilidad del navegador o bifurcar su código para tratar esos problemas. Lo peor que puede pasar es el silencio..

La clase de sonido API

  • jugar()
  • detener()
  • getPath (): Obtiene la ruta del archivo del sonido..
  • getState ()
  • getPan ()
  • setPan (valor): Ajusta la panoramización del sonido (posición estéreo)..
  • getScale ()
  • setScale (valor): Ajusta la escala del sonido (tono)..
  • getVolume ()
  • setVolume (valor): Ajusta el volumen del sonido..
  • pendiente()
  • esta cargando()
  • está cargado()
  • isLooped ()

La clase de sonido también contiene las siguientes funciones estáticas.

  • carga(): Carga sonidos recién creados.
  • detener(): Detiene todos los sonidos.
  • getVolume ()
  • setVolume (valor): Establece el volumen global (maestro).
  • getListener ()
  • setListener (valor): Realiza un seguimiento del progreso de carga de sonido, etc..
  • canPlay (formato): Comprueba si se pueden reproducir varios formatos de sonido.

La documentación se puede encontrar en el código fuente de la demostración..

Conclusión

La reproducción de efectos de sonido en un juego de JavaScript debería ser simple, y este tutorial lo hace así envolviendo la poderosa API de audio web en una clase de sonido rápida y liviana que se encarga de todo por ti..

Recursos Relacionados

Si está interesado en aprender más acerca de las muestras de sonido y cómo manipularlas, he escrito una serie para Tuts + que debería mantenerlo ocupado por un tiempo ...

  1. Creando un sintetizador - Introducción
  2. Creando un sintetizador - Core Engine
  3. Creación de un sintetizador - Procesadores de audio

Los siguientes enlaces son para las especificaciones estandarizadas de W3C y Khronos que están directamente relacionadas con la API de audio web:

  • API de audio web
  • Matrices mecanografiadas