Questa è la parte finale della nostra serie di tutorial sulla creazione di un motore audio basato su sintetizzatore che può essere utilizzato per generare suoni per giochi in stile retrò. Il motore audio può generare tutti i suoni in fase di esecuzione senza la necessità di alcuna dipendenza esterna come file MP3 o file WAV. In questo tutorial, aggiungeremo supporto per processori audio e codice a ritardo processore che può aggiungere un effetto eco decadente ai nostri suoni.
Se non hai già letto il primo tutorial o il secondo tutorial di questa serie, dovresti farlo prima di continuare.
Il linguaggio di programmazione utilizzato in questo tutorial è ActionScript 3.0 ma le tecniche e i concetti utilizzati possono essere facilmente tradotti in qualsiasi altro linguaggio di programmazione che fornisce un'API audio di basso livello.
Assicurati di aver installato Flash Player 11.4 o versioni successive per il tuo browser se desideri utilizzare gli esempi interattivi in questo tutorial.
In questo tutorial finale aggiungeremo processori audio al core engine e creando un semplice processore di delay. La seguente dimostrazione mostra il processore di delay in azione:
Solo un suono viene riprodotto in quella dimostrazione, ma la frequenza del suono viene randomizzata e i campioni audio generati dal motore vengono spinti attraverso un processore di delay, che gli conferisce l'effetto di decadimento dell'eco.
Processore audio
ClasseLa prima cosa che dobbiamo fare è creare una classe base per i processori audio:
pacchetto noise public Class AudioProcessor // public var enabled: Boolean = true; // public function AudioProcessor () if (Object (this) .constructor == AudioProcessor) throw new Error ("La classe AudioProcessor deve essere estesa"); // processo della funzione interna (esempi: Vector.): void
Come puoi vedere, la classe è molto semplice; contiene un interno processi()
metodo che viene invocato dal AudioEngine
classe ogni volta che è necessario elaborare campioni e pubblico abilitato
proprietà che può essere utilizzata per accendere e spegnere il processore.
audiodelay
ClasseIl audiodelay
class è la classe che crea effettivamente il ritardo audio e estende la Processore audio
classe. Ecco la classe vuota di base con cui lavoreremo:
pacchetto noise public class AudioDelay estende AudioProcessor // public function AudioDelay (time: Number = 0.5) this.time = time;
Il tempo
l'argomento passato al costruttore della classe è il tempo (in secondi) del tocco del delay - cioè la quantità di tempo tra ogni ritardo audio.
Ora aggiungiamo le proprietà private:
private var m_buffer: Vector.= nuovo vettore. (); private var m_bufferSize: int = 0; private var m_bufferIndex: int = 0; private var m_time: Number = 0.0; private var m_gain: Number = 0.8;
Il m_buffer
il vettore è fondamentalmente un ciclo di feedback: contiene tutti i campioni audio passati al processi
metodo, e quei campioni sono modificati (in questo caso ridotti in ampiezza) continuamente come m_bufferIndex
passa attraverso il buffer. Questo avrà senso quando arriveremo al processi()
metodo.
Il m_bufferSize
e m_bufferIndex
le proprietà vengono utilizzate per tenere traccia dello stato del buffer. Il m_time
proprietà è l'ora del tocco, in secondi. Il m_gain
proprietà è un moltiplicatore che viene utilizzato per ridurre l'ampiezza dei campioni audio bufferizzati nel tempo.
Questa classe ha solo un metodo, e quello è interno processi()
metodo che sovrascrive il processi()
metodo nel Processore audio
classe:
processo di funzionamento override interno (campioni: Vector.): void var i: int = 0; var n: int = samples.length; var v: Number = 0.0; // mentre io < 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++;
Infine, dobbiamo aggiungere i getter / setter per il privato m_time
e m_gain
proprietà:
get get time della funzione pubblica (): Number return m_time; public set set time (value: Number): void // blocca il tempo nell'intervallo 0,0001 - 8,0 valore = valore < 0.0001 ? 0.0001 : value > 8.0? 8.0: valore; // non è necessario modificare la dimensione del buffer se il tempo non è cambiato se (m_time == valore) return; // imposta l'ora m_time = valore; // aggiorna la dimensione del buffer m_bufferSize = Math.floor (44100 * m_time); m_buffer.length = m_bufferSize;
funzione pubblica get gain (): Number return m_gain; public set set gain (value: Number): void // blocca il guadagno nell'intervallo 0.0 - 1.0 m_gain = value < 0.0 ? 0.0 : value > 1,0? 1.0: valore;
Credi o no, questo è il audiodelay
classe completata. I ritardi audio sono in realtà molto facili una volta capito come il ciclo di feedback (il m_buffer
proprietà) funziona.
AudioEngine
ClasseL'ultima cosa che dobbiamo fare è aggiornare il AudioEngine
classe in modo che possano essere aggiunti processori audio. Prima di tutto aggiungiamo un vettore per memorizzare le istanze del processore audio:
static private var m_processorList: Vector.= nuovo vettore. ();
Per aggiungere e rimuovere effettivamente processori da e verso il AudioEngine
classe useremo due metodi pubblici:
AudioEngine.addProcessor ()
funzione pubblica statica addProcessor (processore: AudioProcessor): void if (m_processorList.indexOf (processor) == -1) m_processorList.push (processor);
AudioEngine.removeProcessor ()
static public function removeProcessor (processor: AudioProcessor): void var i: int = m_processorList.indexOf (processor); if (i! = -1) m_processorList.splice (i, 1);
Abbastanza facile - tutti questi metodi stanno facendo l'aggiunta e la rimozione Processore audio
istanze da o verso il m_processorList
vettore.
L'ultimo metodo che aggiungeremo passerà attraverso l'elenco dei processori audio e, se il processore è abilitato, passa i campioni audio ai processori processi()
metodo:
static private function processSamples (): void var i: int = 0; var n: int = m_processorList.length; // mentre io < n ) if( m_processorList[i].enabled ) m_processorList[i].process( m_sampleList ); i++;
Ora è il momento di aggiungere l'ultimo bit di codice, e questa è una singola riga di codice che deve essere aggiunta al privato onSampleData ()
metodo nel AudioEngine
classe:
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 riga di codice evidenziata è quella che deve essere aggiunta alla classe; semplicemente invoca il processSamples ()
metodo che abbiamo precedentemente aggiunto.
Quello, come si suol dire, è quello. Nel primo tutorial abbiamo dato uno sguardo alle varie forme d'onda e al modo in cui le onde sonore sono memorizzate digitalmente, quindi abbiamo costruito il codice core del motore audio nel secondo tutorial, e ora abbiamo completato il processo con l'aggiunta di processori audio.
C'è molto di più che potrebbe essere fatto con questo codice, o con una variazione di questo codice, ma la cosa importante da tenere a mente è la quantità di lavoro che un motore audio deve fare in fase di runtime. Se spingi un motore audio troppo lontano (e questo è facile da fare), allora le prestazioni complessive del tuo gioco potrebbero risentirne - anche se sposti un motore audio in una sua discussione (o in un worker di ActionScript 3.0) sarà comunque felice morde pezzi dalla CPU se non stai attento.
Tuttavia, molti giochi professionali e non-professionali eseguono molta elaborazione audio in fase di esecuzione perché avere effetti sonori dinamici e musica in un gioco può aggiungere molto all'esperienza complessiva e può attirare il giocatore più in profondità nel gioco mondo. Il motore audio che abbiamo messo insieme in questa serie di tutorial potrebbe altrettanto facilmente funzionare con campioni di effetti sonori regolari (non generati) caricati da file: essenzialmente tutto l'audio digitale è una sequenza di campioni nella sua forma più elementare.
Un'ultima cosa a cui pensare: l'audio è un aspetto molto importante del design del gioco, è altrettanto importante e potente quanto il lato visivo delle cose, e non è qualcosa che dovrebbe essere gettato insieme o imbullonato in un gioco all'ultimo minuto di sviluppo se ti interessa davvero la qualità della produzione dei tuoi giochi. Prenditi il tuo tempo con l'audio design per i tuoi giochi e raccoglierai i frutti.
Spero che questa serie di tutorial ti sia piaciuta e che possa prendere qualcosa di positivo: anche se pensi solo all'audio nei tuoi giochi un po 'più d'ora in poi, ho fatto il mio lavoro.
Tutto il codice sorgente del motore audio è disponibile nel download sorgente.
Divertiti!