Il s'agit du deuxième d'une série de didacticiels dans lesquels nous allons créer un moteur audio basé sur un synthétiseur pouvant générer des sons pour des jeux de style rétro. Le moteur audio générera tous les sons au moment de l'exécution sans recourir à des dépendances externes telles que des fichiers MP3 ou des fichiers WAV. Le résultat final sera une bibliothèque de travail qui pourra être insérée facilement dans vos jeux..
Si vous n'avez pas encore lu le premier tutoriel de cette série, faites-le avant de continuer..
Le langage de programmation utilisé dans ce didacticiel est ActionScript 3.0, mais les techniques et les concepts utilisés peuvent facilement être traduits dans tout autre langage de programmation fournissant une API audio de bas niveau..
Si vous souhaitez utiliser les exemples interactifs de ce didacticiel, assurez-vous que Flash Player 11.4 ou version ultérieure est installé sur votre navigateur..
À la fin de ce didacticiel, tout le code de base requis pour le moteur audio sera terminé. Ce qui suit est une simple démonstration du moteur audio en action.
Un seul son est joué dans cette démonstration, mais la fréquence du son est randomisée avec son temps de relâchement. Le son est également associé à un modulateur pour produire l'effet de vibrato (moduler l'amplitude du son) et la fréquence du modulateur est également randomisée..
La première classe que nous allons créer conservera simplement des valeurs constantes pour les formes d'onde que le moteur audio utilisera pour générer les sons audibles.
Commencez par créer un nouveau package de classe appelé bruit
, puis ajoutez la classe suivante à ce paquet:
paquet noise public final class AudioWaveform statique public const PULSE: int = 0; statique public const SAWTOOTH: int = 1; statique public const SINE: int = 2; statique publique constante TRIANGLE: int = 3;
Nous allons également ajouter une méthode publique statique à la classe qui peut être utilisée pour valider une valeur de forme d'onde. La méthode retournera vrai
ou faux
pour indiquer si la valeur de la forme d'onde est valide ou non.
Fonction publique statique validate (waveform: int): Boolean if (waveform == PULSE) return true; if (waveform == SAWTOOTH) renvoie true; if (waveform == SINE) renvoie true; if (waveform == TRIANGLE) renvoie true; retourne faux;
Enfin, nous devrions empêcher l'instanciation de la classe car il n'y a aucune raison pour quiconque de créer des instances de cette classe. Nous pouvons le faire dans le constructeur de classe:
fonction publique AudioWaveform () renvoie une nouvelle erreur ("La classe AudioWaveform ne peut pas être instanciée");
Ce cours est maintenant terminé.
Empêcher l'instanciation directe de classes de style enum, de classes entièrement statiques et de classes singleton est une bonne chose à faire, car ces types de classe ne doivent pas être instanciés. il n'y a aucune raison de les instancier. Les langages de programmation tels que Java le font automatiquement pour la plupart de ces types de classes, mais actuellement dans ActionScript 3.0, nous devons appliquer ce comportement manuellement dans le constructeur de la classe..
Le suivant sur la liste est le l'audio
classe. Cette classe est de nature similaire à ActionScript 3.0 native. Du son
classe: chaque son de moteur audio sera représenté par un l'audio
instance de classe.
Ajoutez la classe barebone suivante au bruit
paquet:
paquet noise public class Audio fonction publique Audio ()
Les premières choses à ajouter à la classe sont les propriétés qui indiqueront au moteur audio comment générer l'onde sonore chaque fois que le son est joué. Ces propriétés incluent le type de forme d'onde utilisé par le son, la fréquence et l'amplitude de la forme d'onde, la durée du son et son temps de relâchement (à quelle vitesse il disparaît). Toutes ces propriétés seront privées et accessibles via getters / setters:
private var m_waveform: int = AudioWaveform.PULSE; private var m_frequency: Number = 100.0; private var m_amplitude: Number = 0.5; var privée m_duration: Number = 0.2; var privée m_release: Number = 0.2;
Comme vous pouvez le constater, nous avons défini une valeur par défaut raisonnable pour chaque propriété. le amplitude
est une valeur dans la gamme 0.0
à 1,0
, la la fréquence
est en hertz, et le durée
et Libération
les temps sont en secondes.
Nous devons également ajouter deux propriétés privées supplémentaires pour les modulateurs pouvant être attachés au son; encore une fois, ces propriétés seront accessibles via des getters / setters:
private var m_frequencyModulator: AudioModulator = null; private var m_amplitudeModulator: AudioModulator = null;
Finalement, le l'audio
classe contiendra quelques propriétés internes qui ne seront accessibles que par le AudioEngine
classe (nous allons créer cette classe sous peu). Ces propriétés ne doivent pas nécessairement être cachées derrière des accesseurs:
position interne de la variable: Nombre = 0.0; Var interne jouant: Boolean = false; version interne des variables: Boolean = false; échantillons de var interne: vecteur.= null;
le position
est en secondes et il permet à la AudioEngine
Pour que la classe puisse suivre la position du son pendant la lecture du son, ceci est nécessaire pour calculer les échantillons de son de forme d'onde pour le son. le en jouant
et libération
les propriétés disent au AudioEngine
dans quel état est le son, et le des échantillons
property est une référence aux échantillons de forme d'onde mis en cache que le son utilise. L’utilisation de ces propriétés deviendra claire lorsque nous créerons le AudioEngine
classe.
Pour finir le l'audio
classe nous avons besoin d'ajouter les getters / setters:
l'audio.forme d'onde
fonction finale publique get waveform (): int return m_waveform; forme d'onde publique finale de l'ensemble de fonctions (valeur: int): void if (AudioWaveform.isValid (valeur) == false) return; commutateur (valeur) case AudioWaveform.PULSE: samples = AudioEngine.PULSE; Pause; case AudioWaveform.SAWTOOTH: samples = AudioEngine.SAWTOOTH; Pause; case AudioWaveform.SINE: samples = AudioEngine.SINE; Pause; case AudioWaveform.TRIANGLE: samples = AudioEngine.TRIANGLE; Pause; m_waveform = valeur;
l'audio.la fréquence
[Inline] fonction finale publique get frequency (): Number return m_frequency; fréquence finale de la fonction publique finale (valeur: Number): void // fixe la fréquence dans la plage 1,0 - 14080,0 m_frequency = value < 1.0 ? 1.0 : value > 14080.0? 14080.0: valeur;
l'audio.amplitude
[Inline] fonction finale publique get amplitude (): Number return m_amplitude; public final set set amplitude (value: Number): void // fixe l'amplitude à la plage 0.0 - 1.0 m_amplitude = value < 0.0 ? 0.0 : value > 1,0? 1,0: valeur;
l'audio.durée
[Inline] fonction finale publique get duration (): Number return m_duration; durée finale définie de la fonction publique (valeur: Number): void // fixe la durée à la plage 0.0 - 60.0 m_duration = value < 0.0 ? 0.0 : value > 60,0? 60,0: valeur;
l'audio.Libération
[Inline] fonction finale publique get release (): Number return m_release; release de jeu de fonctions public (valeur: Number): void // fixe le temps de relâchement à la plage 0.0 - 10.0 m_release = value < 0.0 ? 0.0 : value > 10,0? 10.0: valeur;
l'audio.fréquenceModulateur
[Inline] fonction finale publique get frequencyModulator (): AudioModulator return m_frequencyModulator; public final function set frequencyModulator (valeur: AudioModulator): void m_frequencyModulator = valeur;
l'audio.amplitudeModulateur
[Inline] fonction finale publique get amplitudeModulator (): AudioModulator return m_amplitudeModulator; ensemble de fonctions final public amplitudeModulator (valeur: AudioModulator): void m_amplitudeModulator = valeur;
Vous avez sans doute remarqué le [En ligne]
balise de métadonnées liée à quelques-unes des fonctions de lecture. Cette balise de métadonnées est une nouvelle fonctionnalité brillante du dernier compilateur ActionScript 3.0 d’Adobe et elle fait ce qui est écrit: elle étend le contenu d’une fonction. Ceci est extrêmement utile pour l'optimisation lorsqu'il est utilisé judicieusement, et générer un son dynamique au moment de l'exécution est certainement quelque chose qui nécessite une optimisation..
Le but de la AudioModulateur
est de permettre l'amplitude et la fréquence de l'audio
instances à moduler pour créer des effets sonores utiles et fous. Les modulateurs sont en fait similaires à l'audio
cas, ils ont une forme d'onde, une amplitude et une fréquence, mais ils ne produisent pas de son audible, ils ne modifient que les sons audibles.
Tout d’abord, créez la classe de barebone suivante dans le bruit
paquet:
paquet noise public class AudioModulator fonction publique AudioModulator ()
Ajoutons maintenant les propriétés privées privées:
private var m_waveform: int = AudioWaveform.SINE; var privée m_frequency: Number = 4.0; private var m_amplitude: Number = 1.0; var privé m_shift: Number = 0.0; var privé m_samples: vecteur.= null;
Si vous pensez que cela ressemble beaucoup à la l'audio
classe alors vous avez raison: tout sauf le décalage
la propriété est la même.
Pour comprendre ce que le décalage
pensez à l’une des formes d’ondes de base utilisées par le moteur audio (impulsion, dents de scie, sinus ou triangle), puis imaginez une ligne verticale traversant la forme d’onde à n’importe quelle position. La position horizontale de cette ligne verticale serait la décalage
valeur; c'est une valeur dans la gamme 0.0
à 1,0
qui indique au modulateur où commencer la lecture de sa forme d'onde et peut, à son tour, avoir un effet profond sur les modifications apportées par le modulateur à l'amplitude ou à la fréquence d'un son.
Par exemple, si le modulateur utilisait une forme d'onde sinusoïdale pour moduler la fréquence d'un son, et décalage
a été fixé à 0.0
, la fréquence du son augmenterait puis diminuerait en raison de la courbure de l'onde sinusoïdale. Cependant, si le décalage
a été fixé à 0.5
la fréquence du son serait d'abord tomber et ensuite augmenter.
Quoi qu'il en soit, revenons au code. le AudioModulateur
contient une méthode interne qui est uniquement utilisée par le AudioEngine
; la méthode est la suivante:
[Inline] processus de fonction finale interne (heure: nombre): nombre var p: int = 0; var s: nombre = 0,0; if (m_shift! = 0.0) time + = (1.0 / m_frequency) * m_shift; p = (44100 * m_frequency * time)% 44100; s = m_samples [p]; retourne s * m_amplitude;
Cette fonction est en ligne parce qu’elle est beaucoup utilisée, et quand je dis "beaucoup", je veux dire 44100 fois par seconde pour chaque son joué auquel un modulateur est attaché (c’est là que la doublure devient incroyablement utile). La fonction récupère simplement un échantillon sonore à partir de la forme d'onde utilisée par le modulateur, ajuste l'amplitude de cet échantillon, puis renvoie le résultat..
Pour finir le AudioModulateur
classe nous avons besoin d'ajouter les getters / setters:
AudioModulateur.forme d'onde
fonction publique get waveform (): int return m_waveform; fonction publique set waveform (valeur: int): void if (AudioWaveform.isValid (valeur) == false) return; commutateur (valeur) case AudioWaveform.PULSE: m_samples = AudioEngine.PULSE; Pause; case AudioWaveform.SAWTOOTH: m_samples = AudioEngine.SAWTOOTH; Pause; case AudioWaveform.SINE: m_samples = AudioEngine.SINE; Pause; case AudioWaveform.TRIANGLE: m_samples = AudioEngine.TRIANGLE; Pause; m_waveform = valeur;
AudioModulateur.la fréquence
fonction publique get frequency (): Number return m_frequency; public function set frequency (valeur: Number): void // fixe la fréquence dans la plage 0.01 - 100.0 m_frequency = value < 0.01 ? 0.01 : value > 100,0? 100,0: valeur;
AudioModulateur.amplitude
fonction publique get amplitude (): Number return m_amplitude; public function set amplitude (value: Number): void // fixe l'amplitude à la plage 0.0 - 8000.0 m_amplitude = value < 0.0 ? 0.0 : value > 8000.0? 8000.0: valeur;
AudioModulateur.décalage
fonction publique get shift (): Number return m_shift; public function set shift (value: Number): void // verrouille le shift dans la plage 0.0 - 1.0 m_shift = value < 0.0 ? 0.0 : value > 1,0? 1,0: valeur;
Et cela termine le AudioModulateur
classe.
Maintenant pour le grand: le AudioEngine
classe. Ceci est une classe entièrement statique et gère à peu près tout ce qui concerne l'audio
instances et génération de son.
Commençons par une classe de base dans le bruit
paquet comme d'habitude:
package noise import flash.events.SampleDataEvent; import flash.media.Sound; import flash.media.SoundChannel; import flash.utils.ByteArray; // public final class AudioEngine fonction publique AudioEngine () renvoie une nouvelle erreur ("La classe AudioEngine ne peut pas être instanciée");
Comme mentionné précédemment, les classes entièrement statiques ne doivent pas être instanciées, d'où l'exception qui est levée dans le constructeur de la classe si quelqu'un tente d'instancier la classe. La classe est aussi final
parce qu'il n'y a aucune raison d'étendre une classe entièrement statique.
Les premières choses qui seront ajoutées à cette classe sont les constantes internes. Ces constantes seront utilisées pour mettre en cache les échantillons pour chacune des quatre formes d'onde utilisées par le moteur audio. Chaque cache contient 44 100 échantillons, ce qui équivaut à un signal hertz. Cela permet au moteur audio de produire des ondes sonores basse fréquence vraiment propres.
Les constantes sont les suivantes:
statique interne fixe PULSE: vecteur.= nouveau vecteur. (44100); statique interne fixe SAWTOOTH: vecteur. = nouveau vecteur. (44100); statique interne fixe SINE: vecteur. = nouveau vecteur. (44100); statique interne constante TRIANGLE: vecteur. = nouveau vecteur. (44100);
Il y a aussi deux constantes privées utilisées par la classe:
statique privée statique BUFFER_SIZE: int = 2048; statique privée fixe SAMPLE_TIME: Number = 1.0 / 44100.0;
le BUFFER_SIZE
correspond au nombre d'échantillons sonores qui seront transmis à l'API son ActionScript 3.0 chaque fois qu'une demande d'échantillons sonores est effectuée. C'est le plus petit nombre d'échantillons permis et le temps de latence du son est le plus bas possible. Le nombre d'échantillons pourrait être augmenté afin de réduire l'utilisation du processeur, mais cela augmenterait la latence du son. le TEMPS D'ÉCHANTILLONNAGE
est la durée d'un seul échantillon sonore, en secondes.
Et maintenant pour les variables privées:
statique privée var m_position: Number = 0.0; statique privée var m_amplitude: Number = 0.5; statique privée var m_soundStream: Sound = null; statique privée var m_soundChannel: SoundChannel = null; statique privée var m_audioList: Vector.
m_position
est utilisé pour suivre le temps du flux sonore, en secondes.m_amplitude
est une amplitude secondaire globale pour tous les l'audio
cas qui jouent.m_soundStream
et m_soundChannel
ne devrait pas avoir besoin d'explication.m_audioList
contient des références à l'audio
cas qui jouent.m_sampleList
est un tampon temporaire utilisé pour stocker des échantillons sonores lorsqu'ils sont demandés par l'API audio ActionScript 3.0..Maintenant, nous devons initialiser la classe. Il y a de nombreuses façons de faire cela, mais je préfère quelque chose de simple et sympa, un constructeur de classe statique:
fonction privée statique $ AudioEngine (): void var i: int = 0; var n: int = 44100; var p: nombre = 0,0; // alors que je < 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 vous avez lu le didacticiel précédent de cette série, vous verrez probablement ce qui se passe dans ce code: les échantillons de chacun des quatre signaux sont générés et mis en cache, et cela ne se produit qu'une fois. Le flux audio est également en cours d'instanciation et de démarrage et sera exécuté en continu jusqu'à la fermeture de l'application..
le AudioEngine
la classe a trois méthodes publiques qui sont utilisées pour jouer et arrêter l'audio
les instances:
AudioEngine.jouer()
fonction publique statique lue (audio: audio): void if (audio.playing == false) m_audioList.push (audio); // cela nous permet de savoir exactement quand le son a été démarré audio.position = m_position - (m_soundChannel.position * 0.001); audio.playing = true; audio.releasing = false;
AudioEngine.Arrêtez()
static public stop function (audio: Audio, allowRelease: Boolean = true): void if (audio.playing == false) // le son n'est pas lu. return; if (allowRelease) // passe à la fin du son et le marque comme relâchant audio.position = audio.duration; audio.releasing = true; revenir; audio.playing = false; audio.releasing = false;
AudioEngine.arrête tout()
Fonction publique statique 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++;
Et voici les principales méthodes de traitement audio, toutes deux privées:
AudioEngine.onSampleData ()
fonction privée statique onSampleData (event: SampleDataEvent): void var i: int = 0; var n: int = BUFFER_SIZE; var s: nombre = 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;
Donc, dans le premier si
déclaration nous vérifions si le m_soundChannel
est toujours nul, et nous devons le faire parce que le SAMPLE_DATA
l'événement est expédié dès que le m_soundStream.play ()
méthode est appelée, et avant que la méthode ait une chance de renvoyer une SoundChannel
exemple.
le tandis que
boucle roule à travers les échantillons sonores qui ont été demandés par m_soundStream
et les écrit à la fourni ByteArray
exemple. Les échantillons sonores sont générés par la méthode suivante:
AudioEngine.generateSamples ()
fonction privée statique 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: nombre = 0,0; var a: nombre = 0,0; var s: nombre = 0,0; var o: Audio = null; // parcourez les instances audio while (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) // l'instance audio s'est arrêtée o.playing = false; j ++; continuer; // l'instance audio est libérée o.releasing = true; // saisir la fréquence et l'amplitude de l'instance audio f = o.frequency; a = amplitude; // if (o.frequencyModulator! = null) // moduler la fréquence f + = o.frequencyModulator.process (o.position); // if (o.amplitudeModulator! = null) // moduler l'amplitude a + = o.amplitudeModulator.process (o.position); // calcule la position dans le cache de formes d'onde p = (44100 * f * o.position)% 44100; // récupère l'échantillon de forme d'onde s = o.samples [p]; // if (o.releasing) // calcule l'amplitude de fondu pour l'échantillon s * = 1.0 - ((o.position - o.duration) / o.release); // ajouter l'échantillon au tampon m_sampleList [j] + = s * a; // met à jour la position de l'instance audio o.position + = SAMPLE_TIME; j ++; i ++;
Enfin, pour finir, nous devons ajouter le getter / setter pour le privé m_amplitude
variable:
fonction publique statique get amplitude (): Number return m_amplitude; static public set set amplitude (value: Number): void // fixe l'amplitude à la plage 0.0 - 1.0 m_amplitude = value < 0.0 ? 0.0 : value > 1,0? 1,0: valeur;
Et maintenant j'ai besoin d'une pause!
Dans le troisième et dernier tutoriel de la série, nous ajouterons processeurs audio le moteur audio. Cela nous permettra d’envoyer tous les échantillons sonores générés via des unités de traitement telles que des limiteurs et des délais durs. Nous examinerons également tout le code pour voir si quelque chose peut être optimisé..
Tout le code source de cette série de tutoriels sera mis à disposition avec le prochain tutoriel.
Suivez-nous sur Twitter, Facebook ou Google+ pour vous tenir au courant des derniers messages..