Esta es la parte final de nuestra serie de tutoriales sobre la creación de un motor de audio basado en sintetizador que se puede utilizar para generar sonidos para juegos de estilo retro. El motor de audio puede generar todos los sonidos en tiempo de ejecución sin la necesidad de dependencias externas, como archivos MP3 o WAV. En este tutorial, agregaremos soporte para procesadores de audio y codificaremos un procesador de retardo lo que puede agregar un efecto de eco decadente a nuestros sonidos.
Si aún no ha leído el primer tutorial o el segundo tutorial de esta serie, debe hacerlo antes de continuar.
El lenguaje de programación utilizado en este tutorial es ActionScript 3.0, pero las técnicas y conceptos utilizados pueden traducirse fácilmente a cualquier otro lenguaje de programación que ofrezca una API de sonido de bajo nivel..
Debe asegurarse de tener instalado Flash Player 11.4 o superior para su navegador si desea usar los ejemplos interactivos de este tutorial..
En este último tutorial iremos añadiendo. procesadores de audio Al núcleo del motor y creando un sencillo procesador de retardo. La siguiente demostración muestra el procesador de retardo en acción:
Solo se reproduce un sonido en esa demostración, pero la frecuencia del sonido se está aleatorizando, y las muestras de audio generadas por el motor se están enviando a través de un procesador de retardo, lo que le da el efecto de eco decreciente.
Procesador de audio
ClaseLo primero que debemos hacer es crear una clase base para los procesadores de audio:
ruido del paquete clase pública AudioProcessor // var público habilitado: Booleano = verdadero; // función pública AudioProcessor () si (Objeto (este) .constructor == AudioProcessor) lanza un nuevo error ("la clase AudioProcessor debe extenderse"); // proceso de la función interna (muestras: Vector.): void
Como puedes ver, la clase es muy simple; contiene un interno proceso()
método que es invocado por el AudioEngine
clase siempre que sea necesario procesar muestras, y una habilitado
Propiedad que se puede utilizar para encender y apagar el procesador..
Retraso auditivo
Claselos Retraso auditivo
clase es la clase que realmente crea el retraso de audio, y extiende la Procesador de audio
clase. Aquí está la clase vacía básica con la que trabajaremos:
ruido del paquete clase pública AudioDelay extiende AudioProcessor // función pública AudioDelay (tiempo: Número = 0.5) this.time = tiempo;
los hora
El argumento pasado al constructor de la clase es el tiempo (en segundos) del toque de retardo, es decir, la cantidad de tiempo entre cada retardo de audio..
Ahora agreguemos las propiedades privadas:
privada var m_buffer: vector.= nuevo vector. (); private var m_bufferSize: int = 0; private var m_bufferIndex: int = 0; privado var m_time: Number = 0.0; var m_gain privado: Número = 0.8;
los m_buffer
El vector es básicamente un bucle de retroalimentación: contiene todas las muestras de audio pasadas al proceso
método, y esas muestras se modifican (en este caso se reducen en amplitud) continuamente como m_bufferIndex
Pasa por el buffer. Esto tendrá sentido cuando lleguemos a la proceso()
método.
los m_bufferSize
y m_bufferIndex
Las propiedades se utilizan para realizar un seguimiento del estado del búfer. los m_time
La propiedad es el tiempo de la pulsación de retardo, en segundos. los m_gain
La propiedad es un multiplicador que se utiliza para reducir la amplitud de las muestras de audio almacenadas en el tiempo..
Esta clase solo tiene un método, y ese es el interno proceso()
método que anula el proceso()
método en el Procesador de audio
clase:
Proceso interno de la función de anulación (muestras: Vector.): void var i: int = 0; var n: int = samples.length; var v: Número = 0.0; // mientras yo < n ) v = m_buffer[m_bufferIndex]; // grab a buffered sample v *= m_gain; // reduce the amplitude v += samples[i]; // add the fresh sample // m_buffer[m_bufferIndex] = v; m_bufferIndex++; // if( m_bufferIndex == m_bufferSize ) m_bufferIndex = 0; // samples[i] = v; i++;
Finalmente, tenemos que agregar los captadores / definidores para el privado m_time
y m_gain
propiedades:
función pública get time (): Number return m_time; hora de ajuste de la función pública (valor: número): void // ajusta la hora al rango 0.0001 - 8.0 valor = valor < 0.0001 ? 0.0001 : value > 8.0? 8.0: valor; // no es necesario modificar el tamaño del búfer si el tiempo no ha cambiado si (m_time == value) return; // establece el tiempo m_time = valor; // actualizar el tamaño del búfer m_bufferSize = Math.floor (44100 * m_time); m_buffer.length = m_bufferSize;
función pública obtener ganancia (): Número return m_gain; ganancia de conjunto de funciones públicas (valor: número): void // fije la ganancia al rango 0.0 - 1.0 m_gain = value < 0.0 ? 0.0 : value > 1,0? 1.0: valor;
Lo creas o no, esa es la Retraso auditivo
clase terminada Los retrasos de audio son en realidad muy fáciles una vez que entiendes cómo el circuito de retroalimentación (el m_buffer
propiedad) obras.
AudioEngine
ClaseLo último que tenemos que hacer es actualizar el AudioEngine
Clase para que se le puedan agregar procesadores de audio. En primer lugar, agreguemos un vector para almacenar las instancias del procesador de audio:
static private var m_processorList: Vector.= nuevo vector. ();
Para agregar y eliminar procesadores desde y hacia el AudioEngine
Clase utilizaremos dos métodos públicos:
AudioEngine.addProcessor ()
función pública estática addProcessor (procesador: AudioProcessor): void if (m_processorList.indexOf (processor) == -1) m_processorList.push (procesador);
AudioEngine.removeProcessor ()
función pública estática removeProcessor (procesador: AudioProcessor): void var i: int = m_processorList.indexOf (processor); if (i! = -1) m_processorList.splice (i, 1);
Bastante fácil: todos los métodos que se están haciendo es agregar y eliminar Procesador de audio
instancias hacia o desde el m_processorList
vector.
El último método que agregaremos recorre la lista de procesadores de audio y, si el procesador está habilitado, pasa las muestras de audio al procesador. proceso()
método:
Función privada estática processSamples (): void var i: int = 0; var n: int = m_processorList.length; // mientras yo < n ) if( m_processorList[i].enabled ) m_processorList[i].process( m_sampleList ); i++;
Ahora es el momento de agregar el bit final de código, y esta es una sola línea de código que debe agregarse a la privada onSampleData ()
método en el AudioEngine
clase:
if (m_soundChannel == null) while (i < n ) b.writeFloat( 0.0 ); b.writeFloat( 0.0 ); i++; return; // generateSamples(); processSamples(); // while( i < n ) s = m_sampleList[i] * m_amplitude; b.writeFloat( s ); b.writeFloat( s ); m_sampleList[i] = 0.0; i++;
La línea de código resaltada es la que se debe agregar a la clase; simplemente invoca el procesoMuestras ()
método que hemos añadido anteriormente.
Eso, como dicen, es eso. En el primer tutorial examinamos varias formas de onda y cómo las ondas de sonido se almacenan digitalmente, luego construimos el código del motor de audio central en el segundo tutorial, y ahora hemos terminado con la adición de procesadores de audio..
Se puede hacer mucho más con este código, o con una variación de este código, pero lo importante a tener en cuenta es la cantidad de trabajo que un motor de audio tiene que hacer en el tiempo de ejecución. Si presionas un motor de audio demasiado lejos (y eso es fácil de hacer), el rendimiento general de tu juego puede verse afectado como consecuencia, incluso si mueves un motor de audio a su propio hilo (o trabajador de ActionScript 3.0), será feliz. Mordiendo trozos fuera de la CPU si no tienes cuidado.
Sin embargo, muchos juegos profesionales y no tan profesionales hacen mucho procesamiento de audio en tiempo de ejecución porque tener efectos de sonido dinámicos y música en un juego puede agregar mucho a la experiencia general, y puede hacer que el jugador se adentre más en el juego. mundo. El motor de audio que reunimos en esta serie de tutoriales podría funcionar fácilmente con muestras de efectos de sonido regulares (no generadas) cargadas de archivos: esencialmente, todo el audio digital es una secuencia de muestras en su forma más básica.
Una última cosa en la que pensar: el audio es un aspecto muy importante del diseño del juego, es tan importante y poderoso como el lado visual de las cosas, y no es algo que deba ser unido o conectado a un juego en el último minuto de Desarrollo si realmente te importa la calidad de producción de tus juegos. Tómese su tiempo con el diseño de audio para sus juegos y obtendrá las recompensas..
Espero que hayas disfrutado de esta serie de tutoriales y puedas quitarle algo positivo: incluso si piensas un poco más en el audio de tus juegos a partir de ahora, entonces he hecho mi trabajo..
Todo el código fuente del motor de audio está disponible en la descarga de la fuente.
Que te diviertas!