Dies ist das zweite einer Reihe von Tutorials, in denen wir eine auf Synthesizer basierende Audio-Engine erstellen werden, die Sounds für Spiele im Retro-Stil erzeugen kann. Die Audio-Engine erzeugt zur Laufzeit alle Sounds, ohne dass externe Abhängigkeiten wie MP3-Dateien oder WAV-Dateien erforderlich sind. Das Endergebnis wird eine funktionierende Bibliothek sein, die mühelos in Ihre Spiele eingefügt werden kann.
Wenn Sie das erste Tutorial dieser Serie noch nicht gelesen haben, sollten Sie dies tun, bevor Sie fortfahren.
Die in diesem Lernprogramm verwendete Programmiersprache ist ActionScript 3.0. Die verwendeten Techniken und Konzepte können jedoch problemlos in jede andere Programmiersprache übersetzt werden, die eine Low-Level-Sound-API bietet.
Sie sollten sicherstellen, dass Flash Player 11.4 oder höher für Ihren Browser installiert ist, wenn Sie die interaktiven Beispiele in diesem Lernprogramm verwenden möchten.
Am Ende dieses Tutorials ist der gesamte für die Audio-Engine erforderliche Kerncode abgeschlossen. Das Folgende ist eine einfache Demonstration der Audio-Engine in Aktion.
In dieser Demonstration wird nur ein Sound gespielt, aber die Frequenz des Sounds wird zusammen mit der Veröffentlichungszeit randomisiert. An den Sound ist auch ein Modulator angeschlossen, um den Vibrato-Effekt zu erzeugen (die Amplitude des Sounds zu modulieren), und die Frequenz des Modulators wird ebenfalls randomisiert.
Die erste Klasse, die wir erstellen werden, enthält einfach konstante Werte für die Wellenformen, die die Audio-Engine zum Erzeugen der hörbaren Sounds verwendet.
Erstellen Sie zunächst ein neues Klassenpaket namens Lärm
, Fügen Sie dem Paket dann die folgende Klasse hinzu:
Paketrauschen public final class AudioWaveform statisch public const PULSE: int = 0; statische public const SAWTOOTH: int = 1; statische öffentliche const SINE: int = 2; statisches öffentliches const TRIANGLE: int = 3;
Wir fügen der Klasse auch eine statische öffentliche Methode hinzu, die zum Validieren eines Wellenformwerts verwendet werden kann. Die Methode wird zurückgegeben wahr
oder falsch
um anzuzeigen, ob der Wellenformwert gültig ist.
statische öffentliche Funktion validate (Wellenform: int): Boolean if (Wellenform == PULSE) gibt true zurück. if (Wellenform == SAWTOOTH) gibt true zurück; if (Wellenform == SINE) gibt true zurück; if (Wellenform == DREIECK) gibt true zurück; falsch zurückgeben;
Schließlich sollten wir verhindern, dass die Klasse instanziiert wird, da für niemanden Grund besteht, Instanzen dieser Klasse zu erstellen. Wir können dies innerhalb des Klassenkonstruktors tun:
public function AudioWaveform () werfen neuen Fehler ("AudioWaveform-Klasse kann nicht instanziiert werden");
Diese Klasse ist jetzt abgeschlossen.
Die direkte Instanziierung von Klassen mit Klassenumgebungen, statischen Klassen und Singleton-Klassen zu verhindern, ist eine gute Sache, da diese Arten von Klassen nicht instanziiert werden sollten. Es gibt keinen Grund, sie zu instanziieren. Programmiersprachen wie Java führen dies für die meisten dieser Klassentypen automatisch durch. In ActionScript 3.0 müssen wir dieses Verhalten jedoch manuell im Klassenkonstruktor erzwingen.
Weiter auf der Liste ist das Audio
Klasse. Diese Klasse ähnelt dem nativen ActionScript 3.0 Klingen
Klasse: Jeder Sound einer Audio-Engine wird durch eine Audio
Klasseninstanz.
Fügen Sie der folgenden Barebones-Klasse hinzu Lärm
Paket:
Paketrauschen public class Audio öffentliche Funktion Audio ()
Die ersten Dinge, die der Klasse hinzugefügt werden müssen, sind Eigenschaften, die der Audio-Engine sagen, wie die Schallwelle erzeugt wird, wenn der Sound abgespielt wird. Diese Eigenschaften umfassen den Typ des Signals, das vom Sound verwendet wird, die Frequenz und Amplitude des Signals, die Dauer des Sounds und seine Release-Zeit (wie schnell sie ausgeblendet wird). Alle diese Eigenschaften sind privat und werden über Getter / Setter abgerufen:
private var m_waveform: int = AudioWaveform.PULSE; private var m_frequency: Number = 100.0; private var m_amplitude: Anzahl = 0,5; private var m_duration: Anzahl = 0,2; private var m_release: Anzahl = 0,2;
Wie Sie sehen, haben wir für jede Eigenschaft einen sinnvollen Standardwert festgelegt. Das Amplitude
ist ein Wert im Bereich 0,0
zu 1,0
, das Frequenz
ist in Hertz, und die Dauer
und Veröffentlichung
Zeiten sind in Sekunden.
Wir müssen auch zwei weitere private Eigenschaften für die Modulatoren hinzufügen, die an den Sound angehängt werden können. Diese Eigenschaften werden wieder über Getter / Setter abgerufen:
private var m_frequencyModulator: AudioModulator = null; private var m_amplitudeModulator: AudioModulator = null;
Endlich, das Audio
Diese Klasse enthält einige interne Eigenschaften, auf die nur die Klasse zugreifen kann AudioEngine
Klasse (wir werden diese Klasse in Kürze erstellen). Diese Eigenschaften müssen nicht hinter Getter / Setter verborgen werden:
interne var Position: Anzahl = 0.0; interne Var-Wiedergabe: Boolean = false; interne var-Freigabe: Boolean = false; interne var-proben: Vektor.= null;
Das Position
ist in Sekunden und erlaubt das AudioEngine
Um die Position des Sounds während der Wiedergabe zu verfolgen, ist dies erforderlich, um die Wellenform-Sound-Samples für den Sound zu berechnen. Das spielen
und freigeben
Eigenschaften sagen der AudioEngine
In welchem Zustand ist der Ton und Proben
Diese Eigenschaft ist eine Referenz auf die zwischengespeicherten Wellenform-Samples, die der Sound verwendet. Die Verwendung dieser Eigenschaften wird klar, wenn wir das erstellen AudioEngine
Klasse.
Um das zu beenden Audio
Klasse müssen wir die Getter / Setter hinzufügen:
Audio.Wellenform
öffentliche letzte Funktion get wave (): int return m_waveform; public final function set wave (value: int): void if (AudioWaveform.isValid (value) == false) return; switch (Wert) case AudioWaveform.PULSE: samples = AudioEngine.PULSE; brechen; case AudioWaveform.SAWTOOTH: Samples = AudioEngine.SAWTOOTH; brechen; case AudioWaveform.SINE: Samples = AudioEngine.SINE; brechen; case AudioWaveform.TRIANGLE: Samples = AudioEngine.TRIANGLE; brechen; m_waveform = Wert;
Audio.Frequenz
[Inline] öffentliche letzte Funktion get frequency (): Number return m_frequency; public final function set frequency (Wert: Number): void // klemmt die Frequenz auf den Bereich 1.0 - 14080.0 m_frequency = value < 1.0 ? 1.0 : value > 14080.0? 14080.0: Wert;
Audio.Amplitude
[Inline] öffentliche letzte Funktion get amplitude (): Number return m_amplitude; public final function set amplitude (value: Number): void // klemmt die Amplitude auf den Bereich 0,0 - 1,0 m_amplitude = value < 0.0 ? 0.0 : value > 1,0? 1,0: Wert;
Audio.Dauer
[Inline] öffentliche letzte Funktion get duration (): Number return m_duration; public final function set duration (value: Number): void // klemmt die Dauer auf den Bereich 0.0 - 60.0 m_duration = value < 0.0 ? 0.0 : value > 60,0? 60,0: Wert;
Audio.Veröffentlichung
[Inline] öffentliche abschließende Funktion get release (): Number return m_release; public function set release (value: Number): void // klemmt die Releasezeit auf den Bereich 0,0 - 10,0 m_release = value < 0.0 ? 0.0 : value > 10,0? 10,0: Wert;
Audio.frequencyModulator
[Inline] public final function get frequencyModulator (): AudioModulator return m_frequencyModulator; public final function set frequencyModulator (Wert: AudioModulator): void m_frequencyModulator = Wert;
Audio.Amplitudenmodulator
[Inline] öffentliche letzte Funktion get amplitudeModulator (): AudioModulator return m_amplitudeModulator; public final function set amplitudeModulator (Wert: AudioModulator): void m_amplitudeModulator = Wert;
Sie haben das zweifellos bemerkt [In der Reihe]
Metadaten-Tag, das an einige Getter-Funktionen gebunden ist. Dieses Metadaten-Tag ist eine glänzende neue Funktion des neuesten ActionScript 3.0-Compilers von Adobe und funktioniert wie es aussieht: Es wird der Inhalt einer Funktion eingebettet (erweitert). Dies ist äußerst nützlich für die Optimierung, wenn es sinnvoll eingesetzt wird. Die Erzeugung von dynamischem Audio zur Laufzeit ist sicherlich etwas, das optimiert werden muss.
Der Zweck der AudioModulator
ist die Amplitude und Frequenz von Audio
Instanzen, die moduliert werden müssen, um nützliche und verrückte Soundeffekte zu erzeugen. Modulatoren sind eigentlich ähnlich Audio
Sie haben zwar eine Wellenform, eine Amplitude und eine Frequenz, erzeugen jedoch keinen hörbaren Klang, sondern modifizieren nur hörbare Geräusche.
Zuerst erstellen Sie die folgende Barebones-Klasse in der Lärm
Paket:
Paketrauschen public class AudioModulator öffentliche Funktion AudioModulator ()
Nun fügen wir die privaten privaten Eigenschaften hinzu:
private var m_waveform: int = AudioWaveform.SINE; private var m_frequency: Anzahl = 4,0; private var m_amplitude: Anzahl = 1,0; private var m_shift: Number = 0.0; private var m_samples: Vektor.= null;
Wenn Sie denken, sieht das sehr ähnlich aus Audio
klasse dann bist du richtig: alles außer dem Verschiebung
Eigenschaft ist das gleiche.
Um zu verstehen, was die Verschiebung
Denken Sie an eine der grundlegenden Wellenformen, die die Audio-Engine verwendet (Impuls, Sägezahn, Sinus oder Dreieck), und stellen Sie sich eine vertikale Linie vor, die an jeder beliebigen Position durch die Wellenform verläuft. Die horizontale Position dieser vertikalen Linie wäre die Verschiebung
Wert; Es ist ein Wert im Bereich 0,0
zu 1,0
Dies gibt dem Modulator an, wo er mit dem Lesen seiner Wellenform beginnen soll, und kann wiederum tiefgreifende Auswirkungen auf die Änderungen haben, die der Modulator an der Amplitude oder Frequenz eines Sounds vornimmt.
Wenn der Modulator beispielsweise eine Sinus-Wellenform verwendet, um die Frequenz eines Tons zu modulieren, und die Verschiebung
wurde an eingestellt 0,0
, Die Frequenz des Klanges würde zuerst steigen und dann aufgrund der Krümmung der Sinuswelle abfallen. Wenn jedoch die Verschiebung
wurde an eingestellt 0,5
Die Klangfrequenz würde zuerst fallen und dann steigen.
Wie auch immer, zurück zum Code. Das AudioModulator
enthält eine interne Methode, die nur vom verwendet wird AudioEngine
; Die Methode ist wie folgt:
[Inline] interner abschließender Funktionsprozess (time: Number): Number var p: int = 0; var s: Anzahl = 0,0; if (m_shift! = 0,0) Zeit + = (1,0 / m_Frequenz) * m_shift; p = (44100 * m_Frequenz * Zeit)% 44100; s = m_samples [p]; return s * m_amplitude;
Diese Funktion ist inline, weil sie häufig verwendet wird. Wenn ich "viel" sage, dann meine ich 44100 Mal pro Sekunde für jeden Sound, an dem ein Modulator angeschlossen ist (hier wird Inlining unglaublich wertvoll). Die Funktion erfasst einfach ein Sound-Sample aus der vom Modulator verwendeten Wellenform, passt die Amplitude dieses Samples an und gibt das Ergebnis zurück.
Um das zu beenden AudioModulator
Klasse müssen wir die Getter / Setter hinzufügen:
AudioModulator.Wellenform
public function get wave (): int return m_waveform; public function set wave (value: int): void if (AudioWaveform.isValid (value) == false) return; switch (value) case AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; brechen; case AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; brechen; case AudioWaveform.SINE: m_samples = AudioEngine.SINE; brechen; case AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; brechen; m_waveform = Wert;
AudioModulator.Frequenz
öffentliche Funktion get frequency (): Number return m_frequency; public function set frequency (value: number): void // klemmt die Frequenz auf den Bereich 0,01 - 100,0 m_frequency = value < 0.01 ? 0.01 : value > 100,0? 100,0: Wert;
AudioModulator.Amplitude
public function get amplitude (): Number return m_amplitude; public function set amplitude (value: number): void // klemmt die Amplitude auf den Bereich 0.0 - 8000.0 m_amplitude = value < 0.0 ? 0.0 : value > 8000,0? 8000.0: Wert;
AudioModulator.Verschiebung
public function get shift (): Number return m_shift; public function set shift (value: Number): void // klemmt die Verschiebung auf den Bereich 0.0 - 1.0 m_shift = value < 0.0 ? 0.0 : value > 1,0? 1,0: Wert;
Und das schließt das ein AudioModulator
Klasse.
Nun zu den Großen: der AudioEngine
Klasse. Dies ist eine rein statische Klasse und verwaltet so ziemlich alles, was damit zusammenhängt Audio
Instanzen und Tonerzeugung.
Beginnen wir mit einer Barebones-Klasse im Lärm
paket wie gewohnt:
Paketrauschen import flash.events.SampleDataEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.utils.ByteArray; // public final class AudioEngine public function AudioEngine () werfen neuen Fehler ("AudioEngine-Klasse kann nicht instanziiert werden");
Wie bereits erwähnt, sollten statische Klassen nicht instantiiert werden. Daher wird die Ausnahme im Klassenkonstruktor ausgelöst, wenn jemand versucht, die Klasse zu instanziieren. Die Klasse ist auch Finale
weil es keinen Grund gibt, eine rein statische Klasse zu erweitern.
Die ersten Dinge, die dieser Klasse hinzugefügt werden, sind interne Konstanten. Diese Konstanten werden verwendet, um die Samples für jede der vier von der Audio-Engine verwendeten Wellenformen zwischenzuspeichern. Jeder Cache enthält 44.100 Samples, was einem Hertz-Signal entspricht. Dadurch kann die Audio-Engine wirklich saubere niederfrequente Schallwellen erzeugen.
Die Konstanten lauten wie folgt:
statische interne const PULSE: Vector.= neuer Vektor. (44100); statische interne const SAWTOOTH: Vektor. = neuer Vektor. (44100); statische interne const SINE: Vector. = neuer Vektor. (44100); statisches internes const DREIECK: Vektor. = neuer Vektor. (44100);
Es gibt auch zwei private Konstanten, die von der Klasse verwendet werden:
statische private const BUFFER_SIZE: int = 2048; statische private const SAMPLE_TIME: Number = 1.0 / 44100.0;
Das PUFFERGRÖSSE
Die Anzahl der Sound-Samples, die an die ActionScript 3.0-Sound-API übergeben werden, wenn eine Anforderung für Sound-Samples gestellt wird. Dies ist die kleinste zulässige Anzahl von Samples und führt zu einer möglichst geringen Klanglatenz. Die Anzahl der Samples könnte erhöht werden, um die CPU-Auslastung zu reduzieren, dies würde jedoch die Klanglatenz erhöhen. Das BEISPIELZEIT
ist die Dauer eines einzelnen Klangbeispiels in Sekunden.
Und nun zu den privaten Variablen:
statische private var m_position: Number = 0.0; statische private var m_amplitude: Anzahl = 0,5; statische private var m_soundStream: Sound = null; statisch privat var m_soundChannel: SoundChannel = null; statische private var m_audioList: Vektor.
m_position
wird verwendet, um die Tonstromzeit in Sekunden zu verfolgen.m_amplitude
ist eine globale sekundäre Amplitude für alle Audio
Instanzen, die spielen.m_soundStream
und m_soundChannel
sollte keine Erklärung brauchen.m_audioList
enthält Verweise auf alle Audio
Instanzen, die spielen.m_sampleList
ist ein temporärer Puffer zum Speichern von Sound-Samples, wenn diese von der ActionScript 3.0-Sound-API angefordert werden.Nun müssen wir die Klasse initialisieren. Es gibt zahlreiche Möglichkeiten, dies zu tun, aber ich bevorzuge etwas Schönes und Einfaches, einen statischen Klassenkonstruktor:
statische private Funktion $ AudioEngine (): void var i: int = 0; var n: int = 44100; var p: Anzahl = 0,0; // während ich < 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();
Wenn Sie das vorherige Tutorial dieser Serie gelesen haben, werden Sie wahrscheinlich sehen, was in diesem Code passiert: Die Samples für jede der vier Wellenformen werden generiert und zwischengespeichert. Dies geschieht nur einmal. Der Sound-Stream wird ebenfalls instanziiert und gestartet und läuft kontinuierlich, bis die App beendet wird.
Das AudioEngine
Die Klasse verfügt über drei öffentliche Methoden, die zum Spielen und Stoppen verwendet werden Audio
Instanzen:
AudioEngine.abspielen()
statische öffentliche Funktionswiedergabe (Audio: Audio): void if (audio.playing == false) m_audioList.push (audio); // Dadurch können wir genau wissen, wann der Sound gestartet wurde. audio.position = m_position - (m_soundChannel.position * 0.001); audio.playing = true; audio.releasing = false;
AudioEngine.halt()
statischer öffentlicher Funktionsstopp (Audio: Audio, allowRelease: Boolean = true): void if (audio.playing == false) // der Sound wird nicht wiedergegeben return; if (allowRelease) // bis zum Ende des Sounds springen und als freizugeben markieren. audio.position = audio.duration; audio.releasing = true; Rückkehr; audio.playing = false; audio.releasing = false;
AudioEngine.stopAll ()
statische öffentliche Funktion stopAll (allowRelease: Boolean = true): void var i: int = 0; var n: int = m_audioList.length; var o: Audio = null; // 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++;
Und hier kommen die wichtigsten Audioverarbeitungsmethoden, die beide privat sind:
AudioEngine.onSampleData ()
statische private Funktion onSampleData (Ereignis: SampleDataEvent): void var i: int = 0; var n: int = BUFFER_SIZE; var s: Anzahl = 0,0; var b: ByteArray = event.data; // if (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;
Also im ersten ob
Aussage wir überprüfen, ob die m_soundChannel
ist immer noch null, und wir müssen das tun, weil die BEISPIELDATEN
Ereignis wird abgesandt, sobald die m_soundStream.play ()
Die Methode wird aufgerufen, und bevor die Methode die Möglichkeit hat, a zurückzugeben SoundChannel
Beispiel.
Das während
Schleife rollt durch die Sound-Samples, die von angefordert wurden m_soundStream
und schreibt sie dem bereitgestellten ByteArray
Beispiel. Die Klangbeispiele werden auf folgende Weise erzeugt:
AudioEngine.generateSamples ()
statische private Funktion generateSamples (): 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: Anzahl = 0,0; var a: Zahl = 0,0; var s: Anzahl = 0,0; var o: Audio = null; // durch die Audio-Instanzen rollen, während (i < 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) // die Audioinstanz wurde beendet o.playing = false; j ++; fortsetzen; // die Audioinstanz gibt o.releasing = true frei; // greife die Frequenz und Amplitude der Audio-Instanz f = o.Frequenz; a = o.amplitude; // if (o.frequencyModulator! = null) // moduliere die Frequenz f + = o.frequencyModulator.process (o.position); // if (o.amplitudeModulator! = null) // moduliere die Amplitude a + = o.amplitudeModulator.process (o.position); // Berechne die Position innerhalb des Wellenformcaches p = (44100 * f * o.position)% 44100; // greife die Wellenformprobe s = o.samples [p]; // if (o.release) // Berechne die Ausblende-Amplitude für die Probe s * = 1.0 - ((o.position - o.duration) / o.release); // füge das Sample zum Puffer hinzu m_sampleList [j] + = s * a; // Aktualisiere die Position der Audio-Instanz o.position + = SAMPLE_TIME; j ++; i ++;
Zum Abschluss müssen wir den Getter / Setter für den Privaten hinzufügen m_amplitude
Variable:
statische öffentliche Funktion get amplitude (): Number return m_amplitude; statische öffentliche Funktionssollamplitude (value: Number): void // klemmt die Amplitude auf den Bereich 0,0 - 1,0 m_amplitude = value < 0.0 ? 0.0 : value > 1,0? 1,0: Wert;
Und jetzt brauche ich eine Pause!
Im dritten und letzten Tutorial der Serie werden wir hinzufügen Audioprozessoren die Audio Engine. Dadurch können wir alle erzeugten Soundsamples durch Verarbeitungseinheiten wie harte Begrenzer und Verzögerungen pushen. Wir werden uns auch den gesamten Code ansehen, um zu sehen, ob alles optimiert werden kann.
Der gesamte Quellcode für diese Tutorialserie wird im nächsten Tutorial verfügbar gemacht.
Folgen Sie uns auf Twitter, Facebook oder Google+, um über die neuesten Beiträge auf dem Laufenden zu bleiben.