Este es el segundo de una serie de tutoriales en los que crearemos un motor de audio basado en sintetizador que puede generar sonidos para juegos de estilo retro. El motor de audio generará todos los sonidos en tiempo de ejecución sin la necesidad de dependencias externas, como archivos MP3 o WAV. El resultado final será una biblioteca funcional que se puede colocar sin esfuerzo en tus juegos.
Si aún no ha leído el primer 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..
Al final de este tutorial, se habrá completado todo el código del núcleo requerido para el motor de audio. La siguiente es una simple demostración del motor de audio en acción..
Solo se reproduce un sonido en esa demostración, pero la frecuencia del sonido se está aleatorizando junto con su tiempo de lanzamiento. El sonido también tiene un modulador adjunto para producir el efecto de vibrato (modular la amplitud del sonido) y la frecuencia del modulador también se está aleatorizando.
La primera clase que crearemos simplemente mantendrá valores constantes para las formas de onda que el motor de audio utilizará para generar los sonidos audibles.
Comience creando un nuevo paquete de clase llamado ruido
, y luego agregue la siguiente clase a ese paquete:
ruido del paquete clase final pública AudioWaveform estática pública const PULSE: int = 0; constata pública estática SAWTOOTH: int = 1; const. public estática SINE: int = 2; const public public static TRIANGLE: int = 3;
También agregaremos un método público estático a la clase que se puede usar para validar un valor de forma de onda, el método devolverá cierto
o falso
para indicar si el valor de la forma de onda es válido o no.
función pública estática validate (waveform: int): Boolean if (waveform == PULSE) devuelve true; if (waveform == SAWTOOTH) devuelve true; if (waveform == SINE) devuelve true; if (waveform == TRIANGLE) devuelve true; falso retorno;
Finalmente, debemos evitar que se cree una instancia de la clase porque no hay razón para que nadie cree instancias de esta clase. Podemos hacer esto dentro del constructor de la clase:
función pública AudioWaveform () lanzar un nuevo error ("la clase AudioWaveform no se puede crear una instancia");
Esta clase esta completa.
Evitar que las clases de estilo de enumeración, las clases totalmente estáticas y las clases de singleton se ejemplifiquen directamente es algo bueno porque estos tipos de clase no deben ser instanciados; No hay ninguna razón para instanciarlos. Los lenguajes de programación como Java lo hacen automáticamente para la mayoría de estos tipos de clase, pero actualmente en ActionScript 3.0 necesitamos implementar este comportamiento manualmente dentro del constructor de la clase..
Siguiente en la lista es el Audio
clase. Esta clase es similar en naturaleza al código nativo de ActionScript 3.0. Sonar
clase: cada sonido de motor de audio será representado por un Audio
instancia de clase.
Agrega la siguiente clase de barebones a la ruido
paquete:
ruido del paquete clase pública Audio función pública Audio ()
Las primeras cosas que deben agregarse a la clase son las propiedades que le dirán al motor de audio cómo generar la onda de sonido cada vez que se reproduce el sonido. Estas propiedades incluyen el tipo de forma de onda utilizada por el sonido, la frecuencia y amplitud de la forma de onda, la duración del sonido y su tiempo de liberación (la rapidez con la que se desvanece). Todas estas propiedades serán privadas y se accederá a través de captadores / configuradores:
private var m_waveform: int = AudioWaveform.PULSE; privada var m_frequency: Number = 100.0; var var_amplitud privada: Número = 0.5; var m_duration privado: Número = 0.2; privado var m_release: número = 0.2;
Como puede ver, hemos establecido un valor predeterminado razonable para cada propiedad. los amplitud
es un valor en el rango 0.0
a 1.0
, la frecuencia
está en hertz, y el duración
y lanzamiento
los tiempos son en segundos.
También necesitamos agregar dos propiedades privadas más para los moduladores que se pueden adjuntar al sonido; una vez más, se podrá acceder a estas propiedades a través de getters / setters:
private var m_frequencyModulator: AudioModulator = null; private var m_amplitudeModulator: AudioModulator = null;
Finalmente, el Audio
clase contendrá algunas propiedades internas a las que solo podrá acceder el AudioEngine
clase (vamos a crear esa clase en breve). Estas propiedades no tienen que estar ocultas detrás de los captadores / definidores:
posición var interna: Número = 0.0; Reproducción de var interna: booleano = falso; liberación var interna: booleano = falso; muestras var internas: vector.= nulo
los posición
Está en segundos y permite la AudioEngine
Para mantener un registro de la posición del sonido mientras se reproduce el sonido, esto es necesario para calcular las muestras de sonido de la forma de onda del sonido. los jugando
y liberando
propiedades le dicen al AudioEngine
en qué estado se encuentra el sonido, y la muestras
La propiedad es una referencia a las muestras de forma de onda en caché que utiliza el sonido. El uso de estas propiedades quedará claro cuando creamos el AudioEngine
clase.
Para terminar el Audio
clase tenemos que añadir los getters / setters:
Audio.forma de onda
función final pública obtener forma de onda (): int return m_waveform; forma de onda del conjunto de funciones final público (valor: int): void if (AudioWaveform.isValid (value) == false) return; switch (valor) caso AudioWaveform.PULSE: samples = AudioEngine.PULSE; descanso; caso AudioWaveform.SAWTOOTH: samples = AudioEngine.SAWTOOTH; descanso; caso AudioWaveform.SINE: samples = AudioEngine.SINE; descanso; caso AudioWaveform.TRIANGLE: samples = AudioEngine.TRIANGLE; descanso; m_waveform = valor;
Audio.frecuencia
[Inline] función final pública obtener frecuencia (): Número return m_frequency; frecuencia de conjunto de funciones final pública (valor: número): void // fije la frecuencia en el rango 1.0 - 14080.0 m_frequency = value < 1.0 ? 1.0 : value > 14080.0? 14080.0: valor;
Audio.amplitud
[Inline] función final pública obtener amplitud (): Número return m_amplitude; amplitud del conjunto de la función pública final (valor: Número): void // fija la amplitud al rango 0.0 - 1.0 m_amplitude = valor < 0.0 ? 0.0 : value > 1,0? 1.0: valor;
Audio.duración
[Inline] función final pública obtener duration (): Number return m_duration; duración final del conjunto de funciones públicas (valor: número): void // fije la duración al rango 0.0 - 60.0 m_duration = value < 0.0 ? 0.0 : value > 60.0? 60.0: valor;
Audio.lanzamiento
[Inline] public final function get release (): Number return m_release; versión de conjunto de funciones públicas (valor: número): void // fije el tiempo de liberación al rango 0.0 - 10.0 m_release = valor < 0.0 ? 0.0 : value > 10.0? 10.0: valor;
Audio.frecuenciamodulador
[Inline] función final pública get frequencyModulator (): AudioModulator return m_frequencyModulator; conjunto de funciones final público frequencyModulator (valor: AudioModulator): void m_frequencyModulator = value;
Audio.amplitudmodulador
[Inline] función final pública get amplitudeModulator (): AudioModulator return m_amplitudeModulator; amplificador de conjunto de funciones final público (valor: AudioModulator): void m_amplitudeModulator = valor;
Usted sin duda notó la [En línea]
Etiqueta de metadatos vinculada a algunas de las funciones de obtención. Esa etiqueta de metadatos es una característica nueva y brillante del último compilador de ActionScript 3.0 de Adobe y hace lo que se dice en la lata: alinea (expande) el contenido de una función. Esto es extremadamente útil para la optimización cuando se usa sensiblemente, y generar audio dinámico en tiempo de ejecución es algo que requiere optimización.
El propósito de AudioModulator
es permitir la amplitud y frecuencia de Audio
Instancias para ser modulado para crear efectos de sonido útiles y locos. Los moduladores son en realidad similares a Audio
Por ejemplo, tienen una forma de onda, una amplitud y una frecuencia, pero en realidad no producen ningún sonido audible, solo modifican los sonidos audibles..
Primero lo primero, crea la siguiente clase de barebones en el ruido
paquete:
ruido del paquete clase pública AudioModulator función pública AudioModulator ()
Ahora agreguemos las propiedades privadas privadas:
private var m_waveform: int = AudioWaveform.SINE; privada var m_frequency: Number = 4.0; var var_amplitud privada: Número = 1.0; var privado variando: número = 0.0; var m_samples privados: Vector.= nulo
Si estás pensando que esto se ve muy similar al Audio
Entonces la clase es correcta: todo excepto el cambio
la propiedad es la misma.
Para entender lo que el cambio
la propiedad, piense en una de las formas de onda básicas que utiliza el motor de audio (pulso, diente de sierra, seno o triángulo) y luego imagine una línea vertical que atraviesa la forma de onda en cualquier posición que desee. La posición horizontal de esa línea vertical sería la cambio
valor; es un valor en el rango 0.0
a 1.0
Eso le dice al modulador dónde comenzar a leer su forma de onda y, a su vez, puede tener un efecto profundo en las modificaciones que el modulador hace a la amplitud o frecuencia de un sonido..
Como ejemplo, si el modulador estaba usando una forma de onda sinusoidal para modular la frecuencia de un sonido, y cambio
se fijó en 0.0
, La frecuencia del sonido primero aumentaría y luego caería debido a la curvatura de la onda sinusoidal. Sin embargo, si el cambio
se fijó en 0.5
La frecuencia del sonido primero caería y luego aumentaría..
De todos modos, volvamos al código. los AudioModulator
Contiene un método interno que solo es usado por el AudioEngine
; el método es como sigue:
[Inline] proceso interno de la función final (tiempo: Número): Número var p: int = 0; var s: Número = 0.0; if (m_shift! = 0.0) time + = (1.0 / m_frequency) * m_shift; p = (44100 * m_frequency * time)% 44100; s = m_samples [p]; return s * m_amplitude;
Esa función está en línea porque se usa mucho, y cuando digo "mucho" me refiero a 44100 veces por segundo por cada sonido que tiene un modulador conectado (aquí es donde la inclinación se vuelve increíblemente valiosa). La función simplemente toma una muestra de sonido de la forma de onda que utiliza el modulador, ajusta la amplitud de esa muestra y luego devuelve el resultado..
Para terminar el AudioModulator
clase tenemos que añadir los getters / setters:
AudioModulator.forma de onda
función pública obtener forma de onda (): int return m_waveform; forma de onda del conjunto de funciones públicas (valor: int): void if (AudioWaveform.isValid (value) == false) return; switch (valor) caso AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; descanso; caso AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; descanso; caso AudioWaveform.SINE: m_samples = AudioEngine.SINE; descanso; caso AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; descanso; m_waveform = valor;
AudioModulator.frecuencia
función pública get frequency (): Number return m_frequency; frecuencia de conjunto de funciones públicas (valor: número): void // fije la frecuencia en el rango 0.01 - 100.0 m_frequency = value < 0.01 ? 0.01 : value > 100.0? 100.0: valor;
AudioModulator.amplitud
función pública obtener amplitud (): Número return m_amplitude; amplitud del conjunto de funciones públicas (valor: número): void // fije la amplitud al rango 0.0 - 8000.0 m_amplitude = valor < 0.0 ? 0.0 : value > 8000.0? 8000.0: valor;
AudioModulator.cambio
función pública get shift (): Number return m_shift; cambio de conjunto de funciones públicas (valor: número): void // restringir el cambio al rango 0.0 - 1.0 m_shift = valor < 0.0 ? 0.0 : value > 1,0? 1.0: valor;
Y eso envuelve la AudioModulator
clase.
Ahora para el grande: el AudioEngine
clase. Esta es una clase completamente estática y gestiona casi todo lo relacionado con Audio
instancias y generación de sonido.
Empecemos con una clase de barebones en el ruido
paquete como de costumbre:
ruido del paquete import flash.events.SampleDataEvent; import flash.media.Sound; importar flash.media.SoundChannel; import flash.utils.ByteArray; // clase final pública AudioEngine función pública AudioEngine () lanzar un nuevo error ("la clase AudioEngine no puede ser instanciada");
Como se mencionó anteriormente, no se deben crear instancias de todas las clases estáticas, de ahí la excepción que se produce en el constructor de la clase si alguien intenta crear una instancia de la clase. La clase es tambien final
Porque no hay razón para extender una clase completamente estática..
Las primeras cosas que se agregarán a esta clase son constantes internas. Estas constantes se utilizarán para almacenar en caché las muestras para cada una de las cuatro formas de onda que utiliza el motor de audio. Cada caché contiene 44.100 muestras, lo que equivale a una forma de onda de hertz. Esto permite que el motor de audio produzca ondas de sonido de baja frecuencia realmente limpias.
Las constantes son las siguientes:
Estática interna const PULSO: vector.= nuevo vector. (44100); Constante interno estático SAWTOOTH: Vector. = nuevo vector. (44100); Estática interna const SINE: Vector. = nuevo vector. (44100); estática interna const TRIANGLE: vector. = nuevo vector. (44100);
También hay dos constantes privadas utilizadas por la clase:
const privada privada BUFFER_SIZE: int = 2048; constante privada estática SAMPLE_TIME: Número = 1.0 / 44100.0;
los TAMAÑO DEL BÚFER
es el número de muestras de sonido que se pasarán a la API de sonido ActionScript 3.0 cada vez que se realice una solicitud de muestras de sonido. Este es el menor número de muestras permitido y produce la latencia de sonido más baja posible. El número de muestras podría aumentarse para reducir el uso de la CPU, pero eso aumentaría la latencia del sonido. los SAMPLE_TIME
es la duración de una sola muestra de sonido, en segundos.
Y ahora para las variables privadas:
estática privada var m_position: Number = 0.0; static private var m_amplitude: Number = 0.5; static private var m_soundStream: Sound = null; static private var m_soundChannel: SoundChannel = null; static private var m_audioList: Vector.
m_position
se utiliza para realizar un seguimiento del tiempo de transmisión de sonido, en segundos.m_amplitud
Es una amplitud secundaria global para todos los Audio
instancias que están jugando.m_soundStream
y m_soundChannel
no debería necesitar ninguna explicación.m_audioList
contiene referencias a cualquier Audio
instancias que están jugando.m_sampleList
es un búfer temporal utilizado para almacenar muestras de sonido cuando son solicitadas por la API de sonido ActionScript 3.0.Ahora, necesitamos inicializar la clase. Hay muchas formas de hacer esto, pero prefiero algo agradable y simple, un constructor de clases estático:
función privada estática $ AudioEngine (): void var i: int = 0; var n: int = 44100; var p: Número = 0.0; // mientras yo < n ) p = i / n; SINE[i] = Math.sin( Math.PI * 2.0 * p ); PULSE[i] = p < 0.5 ? 1.0 : -1.0; SAWTOOTH[i] = p < 0.5 ? p * 2.0 : p * 2.0 - 2.0; TRIANGLE[i] = p < 0.25 ? p * 4.0 : p < 0.75 ? 2.0 - p * 4.0 : p * 4.0 - 4.0; i++; // m_soundStream = new Sound(); m_soundStream.addEventListener( SampleDataEvent.SAMPLE_DATA, onSampleData ); m_soundChannel = m_soundStream.play(); $AudioEngine();
Si ha leído el tutorial anterior en esta serie, probablemente verá lo que está sucediendo en ese código: las muestras para cada una de las cuatro formas de onda se están generando y almacenando en caché, y esto solo ocurre una vez. La secuencia de sonido también se está creando e iniciando y se ejecutará de forma continua hasta que la aplicación finalice..
los AudioEngine
La clase tiene tres métodos públicos que se utilizan para jugar y parar. Audio
instancias:
AudioEngine.jugar()
reproducción de función pública estática (audio: audio): void if (audio.playing == false) m_audioList.push (audio); // esto nos permite saber exactamente cuándo se inició el sonido audio.position = m_position - (m_soundChannel.position * 0.001); audio.playing = true; audio.releasing = falso;
AudioEngine.detener()
parada de la función pública estática (audio: Audio, allowRelease: Boolean = true): void if (audio.playing == false) // el sonido no se está reproduciendo; if (allowRelease) // salta hasta el final del sonido y márcalo como audio.position = audio.duration; audio.releasing = true; regreso; audio.playing = falso; audio.releasing = falso;
AudioEngine.para todo()
función pública estática stopAll (allowRelease: Boolean = true): void var i: int = 0; var n: int = m_audioList.length; var o: Audio = nulo; // if (allowRelease) while (i < n ) o = m_audioList[i]; o.position = o.duration; o.releasing = true; i++; return; while( i < n ) o = m_audioList[i]; o.playing = false; o.releasing = false; i++;
Y aquí vienen los principales métodos de procesamiento de audio, los cuales son privados:
AudioEngine.onSampleData ()
función privada estática onSampleData (evento: SampleDataEvent): void var i: int = 0; var n: int = BUFFER_SIZE; var s: Número = 0.0; var b: ByteArray = event.data; // si (m_soundChannel == null) while (i < n ) b.writeFloat( 0.0 ); b.writeFloat( 0.0 ); i++; return; // generateSamples(); // while( i < n ) s = m_sampleList[i] * m_amplitude; b.writeFloat( s ); b.writeFloat( s ); m_sampleList[i] = 0.0; i++; // m_position = m_soundChannel.position * 0.001;
Así, en la primera Si
declaración que estamos comprobando si el m_soundChannel
sigue siendo nulo, y tenemos que hacer eso porque el DATA DE MUESTRA
evento se envía tan pronto como el m_soundStream.play ()
se invoca el método, y antes de que el método tenga la oportunidad de devolver un Canal de sonido
ejemplo.
los mientras
El bucle recorre las muestras de sonido solicitadas por m_soundStream
y los escribe a los proporcionados. ByteArray
ejemplo. Las muestras de sonido son generadas por el siguiente método:
AudioEngine.generar Muestras ()
función privada estática generaMuestras (): void var i: int = 0; var n: int = m_audioList.length; var j: int = 0; var k: int = BUFFER_SIZE; var p: int = 0; var f: Número = 0.0; var a: Número = 0.0; var s: Número = 0.0; var o: Audio = nulo; // recorre las instancias de audio mientras < n ) o = m_audioList[i]; // if( o.playing == false ) // the audio instance has stopped completely m_audioList.splice( i, 1 ); n--; continue; // j = 0; // generate and buffer the sound samples while( j < k ) if( o.position < 0.0 ) // the audio instance hasn't started playing yet o.position += SAMPLE_TIME; j++; continue; if( o.position >= o.duration) if (o.position> = o.duration + o.release) // la instancia de audio se ha detenido o.playing = false; j ++; continuar; // la instancia de audio está liberando o.releasing = true; // captura la frecuencia y amplitud de la instancia de audio f = o.frequency; a = o.amplitud; // if (o.frequencyModulator! = null) // modular la frecuencia f + = o.frequencyModulator.process (o.position); // if (o.amplitudeModulator! = null) // modular la amplitud a + = o.amplitudeModulator.process (o.position); // calcular la posición dentro del caché de forma de onda p = (44100 * f * o.position)% 44100; // toma la muestra de forma de onda s = o.samples [p]; // if (o.releasing) // calcula la amplitud de desvanecimiento para la muestra s * = 1.0 - ((o.position - o.duration) / o.release); // agregar la muestra al búfer m_sampleList [j] + = s * a; // actualizar la posición de la instancia de audio o.position + = SAMPLE_TIME; j ++; i ++;
Finalmente, para terminar, necesitamos agregar el getter / setter para el privado m_amplitud
variable:
función pública estática obtener amplitud (): Número return m_amplitude; amplitud del conjunto de funciones públicas estáticas (valor: Número): void // fija la amplitud al rango 0.0 - 1.0 m_amplitude = valor < 0.0 ? 0.0 : value > 1,0? 1.0: valor;
Y ahora necesito un descanso.!
En el tercer y último tutorial de la serie iremos añadiendo. procesadores de audio El motor de audio. Esto nos permitirá impulsar todas las muestras de sonido generadas a través de unidades de procesamiento como limitadores duros y retrasos. También veremos todo el código para ver si se puede optimizar algo..
Todo el código fuente de esta serie de tutoriales estará disponible con el siguiente tutorial.
Síganos en Twitter, Facebook o Google+ para mantenerse al día con las últimas publicaciones..