Bandes sonores séquentielles et dynamiques pour les jeux

Dans ce tutoriel, nous allons examiner une technique permettant de construire et de séquencer de la musique dynamique pour les jeux. La construction et le séquencement ont lieu à l'exécution, permettant aux développeurs de jeux de modifier la structure de la musique pour refléter ce qui se passe dans le monde du jeu..

Avant d'entrer dans les détails techniques, vous souhaiterez peut-être jeter un coup d'œil à une démonstration pratique de cette technique en action. La musique de la démonstration est construite à partir d'une collection de blocs audio individuels qui sont séquencés et mélangés au moment de l'exécution pour former la piste musicale complète..

Cliquez pour voir la démo.

Cette démonstration nécessite un navigateur Web prenant en charge l'API Web Audio du W3C et l'audio OGG. Google Chrome est le meilleur navigateur à utiliser pour afficher cette démonstration, mais Firefox Aurora peut également être utilisé..

Si vous ne pouvez pas visionner la démo ci-dessus dans votre navigateur, vous pouvez également regarder cette vidéo sur YouTube:



Vue d'ensemble

Le fonctionnement de cette technique est assez simple, mais il est possible d’ajouter de la musique dynamique vraiment sympa aux jeux si elle est utilisée de manière créative. Il permet également de créer des pistes de musique infiniment longues à partir d'un fichier audio relativement petit..

La musique originale est essentiellement décomposée en une collection de blocs, d’une longueur maximale d’une mesure, et ces blocs sont stockés dans un seul fichier audio. Le séquenceur musical charge le fichier audio et extrait les échantillons audio bruts dont il a besoin pour reconstruire la musique. La structure de la musique est dictée par un ensemble de tableaux mutables qui indiquent au séquenceur quand jouer les blocs de musique..

Vous pouvez considérer cette technique comme une version simplifiée d'un logiciel de séquençage tel que Reason, FL Studio ou Dance EJay. Vous pouvez également considérer cette technique comme l’équivalent musical des briques Lego..


Structure de fichier audio

Comme mentionné précédemment, le séquenceur musical nécessite que la musique originale soit décomposée en une collection de blocs, et que ces blocs doivent être stockés dans un fichier audio..

Cette image montre comment les blocs peuvent être stockés dans un fichier audio.

Vous pouvez voir sur cette image que cinq fichiers individuels sont stockés dans le fichier audio et que tous les blocs ont la même longueur. Pour que les choses restent simples pour ce tutoriel, les blocs ont tous une barre de long.

L'ordre des blocs dans le fichier audio est important car il détermine les canaux de séquenceur auxquels les blocs sont affectés. Le premier bloc (par exemple, batterie) sera attribué au premier canal du séquenceur, le deuxième bloc (par exemple, des percussions) sera affecté au deuxième canal du séquenceur, etc..


Canaux séquenceurs

Un canal de séquenceur représente une rangée de blocs et contient des indicateurs (un pour chaque mesure de musique) indiquant si le bloc affecté au canal doit être lu. Chaque drapeau est une valeur numérique et vaut zéro (ne pas jouer le bloc) ou un (jouer le bloc).

Cette image montre la relation entre les blocs et les canaux du séquenceur.

Les chiffres alignés horizontalement au bas de l'image ci-dessus représentent des nombres en barres. Comme vous pouvez le constater, dans le premier bar de musique (01) seul le bloc Guitar sera joué, mais dans la cinquième mesure (05) les blocs batterie, percussion, basse et guitare seront joués.


La programmation

Dans ce tutoriel, nous ne passerons pas en revue le code d’un séquenceur musical complet; au lieu de cela, nous examinerons le code de base requis pour faire fonctionner un séquenceur musical simple. Le code sera présenté comme un pseudo-code pour que les choses soient aussi agnostiques que possible..

Avant de commencer, vous devez garder à l'esprit que le langage de programmation que vous décidez d'utiliser nécessite une API permettant de manipuler l'audio à un niveau bas. Un bon exemple de ceci est l’API Web Audio disponible en JavaScript..

Vous pouvez également télécharger les fichiers source joints à ce didacticiel pour étudier une implémentation JavaScript d'un séquenceur musical de base créé à titre de démonstration pour ce didacticiel..

Récapitulation rapide

Nous avons un seul fichier audio qui contient des blocs de musique. Chaque bloc de musique a une longueur d'un bar et l'ordre des blocs dans le fichier audio dicte le canal du séquenceur auquel les blocs sont affectés..

Constantes

Nous aurons besoin de deux informations avant de pouvoir procéder. Nous devons connaître le tempo de la musique, en battements par minute, ainsi que le nombre de battements dans chaque mesure. Ce dernier peut être considéré comme la signature temporelle de la musique. Ces informations doivent être stockées sous forme de valeurs constantes car elles ne changent pas pendant l'exécution du séquenceur musical..

 TEMPO = 100 // battements par minute SIGNATURE = 4 // battements par barre

Nous devons également connaître le taux d'échantillonnage utilisé par l'API audio. Cela correspond généralement à 44100 Hz, car cela convient parfaitement à l'audio, mais le matériel de certaines personnes est configuré pour utiliser une fréquence d'échantillonnage plus élevée. L'API audio que vous choisissez d'utiliser doit fournir ces informations, mais pour les besoins de ce didacticiel, nous supposerons que la fréquence d'échantillonnage est de 44100 Hz..

 SAMPLE_RATE = 44100 // Hertz

Nous pouvons maintenant calculer la longueur d’échantillon d’une barre de musique, c’est-à-dire le nombre d’échantillons audio dans un bloc de musique. Cette valeur est importante car elle permet au séquenceur musical de localiser les blocs individuels de musique et les échantillons audio de chaque bloc dans les données du fichier audio..

 BLOCK_SIZE = floor (SAMPLE_RATE * (60 / (TEMPO / SIGNATURE)))

Flux audio

L'API audio que vous choisissez d'utiliser dictera la manière dont les flux audio (tableaux d'échantillons audio) sont représentés dans votre code. Par exemple, l’API Web Audio utilise des objets AudioBuffer.

Pour ce tutoriel, il y aura deux flux audio. Le premier flux audio sera en lecture seule et contiendra tous les échantillons audio chargés à partir du fichier audio contenant les blocs de musique. Il s'agit du flux audio "d'entrée"..

Le second flux audio sera en écriture seule et sera utilisé pour envoyer des échantillons audio au matériel. c'est le flux audio "de sortie". Chacun de ces flux sera représenté par un tableau unidimensionnel.

 entrée = […] sortie = […]

Le processus exact requis pour charger le fichier audio et extraire les échantillons audio du fichier sera dicté par le langage de programmation que vous utilisez. Dans cet esprit, nous assumerons le contribution Le tableau de flux audio contient déjà les échantillons audio extraits du fichier audio.

le sortie Le flux audio a généralement une longueur fixe, car la plupart des API audio vous permettent de choisir la fréquence à laquelle les échantillons audio doivent être traités et envoyés au matériel. mettre à jour la fonction est appelée. La fréquence est normalement directement liée à la latence de l'audio, les hautes fréquences nécessiteront plus de puissance du processeur, mais elles réduiront la latence, et inversement..

Données du séquenceur

Les données du séquenceur sont un tableau multidimensionnel. chaque sous-réseau représente un canal de séquenceur et contient des indicateurs (un pour chaque mesure de musique) indiquant si le bloc de musique affecté au canal doit être lu ou non. La longueur des tableaux de canaux dicte également la longueur de la musique.

 canaux = [[0,0,0,0, 0,0,0,0, 1,1,1,1, 1,1,1,1], // tambours [0,0,0,0, 1 , 1,1,1, 1,1,1,1, 1,1,1,1], // percussion [0,0,0,0, 0,0,0,0, 1,1,1, 1, 1,1,1,1], // basse [1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1], // guitare [0,0,0,0, 0,0,1,1, 0,0,0,0, 0,0,1,1] // chaînes de caractères]

Les données que vous voyez ici représentent une structure musicale de seize mesures. Il contient cinq canaux, un pour chaque bloc de musique du fichier audio, et les canaux sont dans le même ordre que les blocs de musique du fichier audio. Les drapeaux dans les tableaux de canaux nous permettent de savoir si le bloc assigné aux canaux doit être joué ou non: la valeur 0 signifie qu'un bloc ne sera pas joué; la valeur 1 signifie qu'un bloc sera joué.

Cette structure de données est modifiable, elle peut être modifiée à tout moment même lorsque le séquenceur musical est en cours d'exécution, ce qui vous permet de modifier les indicateurs et la structure de la musique pour refléter ce qui se passe dans un jeu..

Traitement audio

La plupart des API audio diffuseront un événement vers une fonction de gestionnaire d'événements ou invoqueront directement une fonction lorsqu'il faudra envoyer davantage d'échantillons audio sur le matériel. Cette fonction est généralement invoquée constamment comme la boucle de mise à jour principale d'un jeu, mais pas aussi fréquemment. Vous devez donc passer du temps à l'optimiser..

Fondamentalement, ce qui se passe dans cette fonction est:

  1. Plusieurs échantillons audio sont extraits de la contribution flux audio.
  2. Ces échantillons sont ajoutés pour former un seul échantillon audio..
  3. Cet échantillon audio est poussé dans le sortie flux audio.

Avant d'entrer dans les entrailles de la fonction, nous devons définir quelques variables supplémentaires dans le code:

 playing = true // indique si la musique (le séquenceur) joue la position = 0 // la position de la tête de lecture du séquenceur, en échantillons

le en jouant Boolean nous indique simplement si la musique est en cours de lecture. s'il ne joue pas, nous devons pousser des échantillons audio silencieux dans le sortie flux audio. le position garde la trace de la position de la tête de lecture dans la musique, c'est donc un peu comme un scrubber sur un lecteur de musique ou de vidéo typique.

Maintenant pour les entrailles de la fonction:

 function update () outputIndex = 0 outputCount = output.length if (lecture == false) // les échantillons silencieux doivent être placés dans le flux de sortie while (outputIndex < outputCount )  output[ outputIndex++ ] = 0.0  // the remainder of the function should not be executed return  chnCount = channels.length // the length of the music, in samples musicLength = BLOCK_SIZE * channels[ 0 ].length while( outputIndex < outputCount )  chnIndex = 0 // the bar of music that the sequencer playhead is pointing at barIndex = floor( position / BLOCK_SIZE ) // set the output sample value to zero (silent) output[ outputIndex ] = 0.0 while( chnIndex < chnCount )  // check the channel flag to see if the block should be played if( channels[ chnIndex ][ barIndex ] == 1 )  // the position of the block in the "input" stream inputOffset = BLOCK_SIZE * chnIndex // index into the "input" stream inputIndex = inputOffset + ( position % BLOCK_SIZE ) // add the block sample to the output sample output[ outputIndex ] += input[ inputIndex ]  chnIndex++  // advance the playhead position position++ if( position >= musicLength) // réinitialise la position de la tête de lecture pour mettre en boucle la position de la musique = 0 outputIndex ++

Comme vous pouvez le constater, le code requis pour traiter les échantillons audio est assez simple, mais comme ce code sera exécuté plusieurs fois par seconde, vous devez rechercher des moyens d’optimiser le code dans la fonction et de pré-calculer autant de valeurs que possible. Les optimisations que vous pouvez appliquer au code dépendent uniquement du langage de programmation que vous utilisez..

N'oubliez pas que vous pouvez télécharger les fichiers source joints à ce didacticiel si vous souhaitez examiner une façon de mettre en œuvre un séquenceur musical de base en JavaScript à l'aide de l'API Web Audio..


Remarques

Le format du fichier audio que vous utilisez doit permettre à l’audio de fonctionner en boucle de manière transparente. En d'autres termes, l'encodeur utilisé pour générer le fichier audio ne doit pas injecter de bourrage (morceaux audio silencieux) dans le fichier audio. Malheureusement, les fichiers MP3 et MP4 ne peuvent pas être utilisés pour cette raison. Les fichiers OGG (utilisés par la démonstration JavaScript) peuvent être utilisés. Vous pouvez également utiliser des fichiers WAV si vous le souhaitez, mais ils ne constituent pas un choix judicieux pour les jeux ou les applications Web en raison de leur taille..

Si vous programmez un jeu et si le langage de programmation que vous utilisez pour le jeu prend en charge la simultanéité (threads ou ouvriers), vous pouvez envisager d'exécuter le code de traitement audio dans son propre thread ou ouvrier s'il est possible de le faire. Faire cela soulagera la boucle de mise à jour principale du jeu de toute surcharge de traitement audio susceptible de se produire.


Musique dynamique dans les jeux populaires

Ce qui suit est une petite sélection de jeux populaires qui tirent parti d’une musique dynamique d’une manière ou d’une autre. La mise en œuvre de ces jeux pour leur musique dynamique peut varier, mais le résultat final est le même: les joueurs du jeu ont une expérience de jeu plus immersive..

  • Voyage: thatgamecompany.com
  • Fleur: thatgamecompany.com
  • LittleBigPlanet: littlebigplanet.com
  • Portail 2: thinkwithportals.com
  • PixelJunk Shooter: pixeljunk.jp
  • Red Dead Redemption: rockstargames.com
  • Uncharted: naughtydog.com

Conclusion

Donc, voilà - une simple mise en œuvre de musique séquentielle dynamique qui peut vraiment améliorer la nature émotionnelle d'un jeu. La façon dont vous décidez d’utiliser cette technique et la complexité du séquenceur dépendent de votre choix. Cette mise en œuvre simple peut prendre de nombreuses directions et nous en couvrirons certaines dans un prochain tutoriel..

Si vous avez des questions, n'hésitez pas à les poster dans les commentaires ci-dessous et je reviendrai vers vous dès que possible..