Partie 4 : Le son
Développement amateur sur Nintendo DS Partie 4
La partie 3 de cette série de tutoriaux vous a montré comment gérer les touches de la DS. Cette partie va vous expliquer comment utiliser les capacités audio de la DS et jouer des échantillons sonores.
Jouer des sons demande d'avoir un peu de bagage dans la connaissance de la DS. Seul l'ARM7 peut accéder au hardware sonore donc nous allons avoir à prévenir l'ARM7 depuis l'ARM9 qu'il doit jouer un son. Nous allons aussi avoir besoin de stocker les fichiers sons quelque part et d'y accéder depuis l'ARM7.
Pour conserver un tutorial assez court, je vais seulement expliquer comment jouer un son depuis l'ARM7 et un tutorial à venir expliquera comment signaler à l'ARM7 depuis l'ARM9 qu'il doit jouer un son.
Les registres Son et les Macros
Il y a un certain nombre de registres sonores utilisés pour réaliser des sons. La librairie libnds possède des macros pour faciliter l'utilisation de ces registres.
| Macro | Description |
|---|---|
| SOUND_VOL(n) | Change le volume d'un canal. Les valeurs peuvent aller de 0x00 (silence) à 0x7f (volume maximum). |
| SOUND_FREQ(n) | Fixe la fréquence d'un échantillon sonore à jouer. |
| SOUND_ENABLE | Autorise à jouer des sons. |
| SCHANNEL_ENABLE | Autorise à jouer des sons sur un certain canal sonore. |
| SOUND_ONE_SHOT | Un flag indiquant si l'échantillon sonore doit être joué une fois ou en boucle. |
| SOUND_CR | Registre de configuration par défaut de tous les canaux sonores (Autorisation, volume, etc.). |
| SCHANNEL_TIMER(c) | Détermine la fréquence de play-back d'un échantillon. Le positionner à la valeur de SOUND_FREQ(n) va jouer l'échantillon à la fréquence voulue. 'c' est le canal sonore (0-15). |
| SCHANNEL_SOURCE(c) | Le pointeur vers les données sonores. |
| SCHANNEL_LENGTH(c) | La longueur en 'words' (un 'word' équivaut à 32 bits) des données de l'échantillon. |
| SCHANNEL_CR(c) | Permet de positionner certaines informations comme le volume, si on joue en boucle ou pas, l'autorisation, etc. pour un canal particulier. |
Voici un exemple avec un échantillon 8bit, enregistré à 22,050Hz, avec lequel 'sample' pointe vers le début des données, et 'sample_end' la fin, et le tout étant à jouer sur le canal 0 :
SCHANNEL_TIMER(0) = SOUND_FREQ(22050); SCHANNEL_SOURCE(0) = (uint32)sample; SCHANNEL_LENGTH(0) = ((int)sample_end - (int)sample) >> 2; SCHANNEL_CR(0) = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_8BIT | SOUND_VOL(0x3F);
La longueur de l'échantillon est divisée par 4 (le '>> 2' réalise cette opération) car le calcul de la longueur, réalisé en soustrayant le début de la fin se fait en octet. Nous en avons besoins en 'words' (23 bits).
Convertir et stocker les échantillons
Les échantillons doivent être stockés quelque part pour être lu et joué. Le moyen le plus simple est de lier les données directement à l'exécutable. Cependant, cette méthode limite la taille des données que nous pouvons stocker (La DS ne possède que 4MB de RAM). J'explorerais d'autres solutions dans de prochains tutoriaux.
Les échantillons doivent aussi être au format 'raw'. La plupart des formats musicaux sont compressés et possèdent des informations en entête. Nous souhaitons avoir des données non compressées. J'utilise pour cela un outil source nommé 'Sox' pour convertir les fichiers 'WAV' vers le format 'RAW'. Une version Win32 de Sox est disponible à l'adresse du site web de Sox ou sur ce site.
Sox est très simple à utiliser, et réalise par défaut ce que nous volons faire pour convertir un fichier WAV en RAW :
d:\sound_demo1\wav>sox -V Ufo.wav Ufo.raw sox: Detected file format type: wav sox: WAV Chunk fmt sox: WAV Chunk data sox: Reading Wave file: Microsoft PCM format, 1 channel, 11127 samp/sec sox: 11127 byte/sec, 1 block align, 8 bits/samp, 1802 data bytes sox: Input file Ufo.wav: using sample rate 11127 size bytes, encoding unsigned, 1 channel sox: Output file Ufo.raw: using sample rate 11127 size bytes, encoding unsigned, 1 channel sox: Output file: comment "Processed by SoX"
En utilisant l'option '-V', nous obtenons le mode bavard ('verbose') qui nous indiquent la fréquence à utiliser pour jouer l'échantillon sur DS. Dans notre cas, c'est 11 127.
Le fichier raw produit par Sox oit être converti en un fichier 'objet' avant d'être inclue à l'exécutable de l'ARM7. Ce fichier 'objet' contient des symboles que notre programme peut utiliser pour pour obtenir le début et la fin des données. La commande qui rélise cette action se nomme 'arm-elf-objcopy'. Cette commande prenant en compte un nombre non négligeable d'arguments, je vous donne un exemple et je vous l'explique ensuite :
arm-elf-objcopy -I binary -O elf32-littlearm -B arm \
--rename-section .data=.rodata,readonly,data,contents,alloc \
--redefine-sym _binary_Ufo_raw_start=sound_ufo \
--redefine-sym _binary_Ufo_raw_end=sound_ufo_end \
--redefine-sym _binary_Ufo_raw_size=sound_ufo_size \
Ufo.raw Ufo.o
Les 3 premiers arguments '-I binary', '-O elf32-littlearm' et '-B arm' ne servent qu'à définir le format de sortie de type fichier objet ARM.
Le 'rename-section' est utilisé pour définir l'emplacement en mémoire où seront stockées les données.
Par défaut, arm-elf-objcopy, crée les symboles suivant (dans lesquels 'filename' et 'ext' sont remplacés respectivement par le fichier en entrée et son extension) :
- _binary_filename_ext_start
- _binary_filename_ext_end
- _binary_filename_ext_size
L'option 'redefine-sym' renomment ces symboles en quelque chose de plus lisible et accessible pour notre programme. Le résultat de l'exécution de cette commande produit un fichier 'Ufo.o' qui peut être linké à notre programme (Notez qu'il doit être linké dans le binaire de l'ARM7) :
arm-elf-g++ -g -mthumb-interwork -mno-fpu \ -specs=ds_arm7.specs arm7_main.o <span class="highlite">wav/Ufo.o</span> \ -Ld:\devkitpro\libnds\lib -lnds7 -o arm7.elf
Un pointeur vers les données peut être obtenu en lisant le symbole 'sound_ufo'. Une macro très simple permet de créer les définitions de type 'extern' pour ces symboles dans notre programme :
#define EXTERNAL_DATA(name) \ extern const uint8 name[]; \ extern const uint8 name##_end[]; \ extern const uint32 name##_size EXTERNAL_DATA(sound_ufo);
Les données peuvent alors être utilisées en entrée des registres de son. Notez la fréquence de 11127, obtenue par l'affichage en sortie de Sox :
SCHANNEL_TIMER(0) = SOUND_FREQ(11127); SCHANNEL_SOURCE(0) = (uint32)sound_ufo; SCHANNEL_LENGTH(0) = ((int)sound_ufo_end - (int)sound_ufo) >> 2; SCHANNEL_CR(0) = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_8BIT | SOUND_VOL(0x3F);
Linker les échantillons sonores dans l'exécutable de l'ARM7 n'est pas la meilleur chose à faire dans la réalité mais permet de tester facilement notre programme. J'ai lu dans un message du forum GBADEV forums que dans le DevKitPRO, l'exécutable de l'ARM7 est limité à 64KB de code. Ainsi, même de petits échantillons peuvent atteindre cette limite. Vous pouvez aussi linker ces échantillons dans l'ARM9 ou utiliser un système de fichiers. Dans ces deux derniers cas, vous devez envoyer des messages depuis l'ARM9 vers l'ARM7 avec un pointeur vers les données à jouer. Cette partie est expliquée en détail dans le tutorial six.
Exemple simple
Le nom du programme mettant en pratique tout ceci se nomme 'sound_demo1'. Le code pour l'ARM9 est dans arm9_main.cpp et est le même code pour pour le Tutorial Trois.
Le code de l'ARM7 code, dans arm7_main.cpp est le code relatif à la gestion sonore. C'est en gros le code par défaut de l'ARM7 présentés dans les précédents tutoriaux avec le code gérant le son ajouté. La premeière chose à réaliser se trouve dans le 'main', pour 'allumer' et mettre les attributs par défaut du son :
Les données sonores que j'ai utilisé sont des fichiers WAV qui sont des échantillons enregistrés du jeu d'arcade 'space invader'. Ces fichiers WAV sont :
Ils sont convertis en fichiers objets comme décrit précédemment et les symboles sont mis à disposition de l'ARM7 en utilisant la macro EXTERNAL_DATA :
EXTERNAL_DATA(sound_base_hit); EXTERNAL_DATA(sound_inv_hit); EXTERNAL_DATA(sound_shot); EXTERNAL_DATA(sound_ufo); EXTERNAL_DATA(sound_ufo_hit); EXTERNAL_DATA(sound_walk1); EXTERNAL_DATA(sound_walk2); EXTERNAL_DATA(sound_walk3); EXTERNAL_DATA(sound_walk4);
Dans l'interruption VBL, je joue seulement les sons si X ou Y sont appuyés. Chaque touche joue un son différent sur un canal différent. Un son, sound_ufo_hit, dure quelques secondes alors que l'autre, sound_inv_hit, est court. En appuyant sur 'X', vous jouez le son long et 'Y' le court. Comme ils sont joués sur des canaux différents, ils peuvent être joués en même temps :
// Y Key if((~XKEYS) & (1 << 1)) { SCHANNEL_TIMER(0) = SOUND_FREQ(11127); SCHANNEL_SOURCE(0) = (uint32)sound_inv_hit; SCHANNEL_LENGTH(0) = ((int)sound_inv_hit_end - (int)sound_inv_hit) >> 2; SCHANNEL_CR(0) = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_8BIT | SOUND_VOL(0x3F); } // X Key if((~XKEYS) & (1 << 0)) { SCHANNEL_TIMER(1) = SOUND_FREQ(11127); SCHANNEL_SOURCE(1) = (uint32)sound_ufo_hit; SCHANNEL_LENGTH(1) = ((int)sound_ufo_hit_end - (int)sound_ufo_hit) >> 2; SCHANNEL_CR(1) = SCHANNEL_ENABLE | SOUND_ONE_SHOT | SOUND_8BIT | SOUND_VOL(0x3F); }
La gestion des touches doit vous être familière depuis le dernier tutorial.
Construction de l'exécutable
Il reste une étape supplémentaire pour construire notre exécutable, il faut convertir les ficheirs WAV. Ce fichier Makefile très simple vous permet d'exécuter les commandes de conversion ainsi que les commandes du compilateur.
Le code source complet se trouve dans sound_demo1.zip et vous pouvez télécharger les fichiers sound_demo1.nds et sound_demo1.nds.gba pour tester sur émulateur et hardware. Il n'y a pas beaucoup de choix pour le faire fonctionner sur émulateur en ce moment, seul Ideas supporte le son.
Problèmes
Après la compilation des fichiers et lors de l'exécution, vous pouvez obtenir 'deux écrans blancs' sur DS. Si cela arrive, cela veut certainement dire que vous utilisez une version trop ancienne de 'DevKitPRO', probablement une livrée avec l'environnement 'ndsdev' avant la version 2.4.0. Mettez à jour votre 'DevKitPRO' et le problème sera corrigé.
Conclusion
Ce tutorial vous a montré comment jouer de petits échantillons sonores depuis l'ARM7 et comment les stocker dans l'exécutable.
Il reste encore beaucoup de chose à dire sur le son et de nombreuses questions restent en suspend. Voici certains points à travailler (et j'apprécierais des informations sur ces points si elles sont disponibles ) :
- Comment jouer des sons stéréo, passant de la gauche à la droite,etc.
- Comment stocker et lire les données sonores du fichier .NDS plutôt que de les linker à l'exécutable (le format de ficheirs NDS possède un mini gestionnaire de fichiers).
- Est-il possible de générer les sons plutôt que de jouer des échantillons ? Par exemple, de gros morceaux de musiques générés qui jouent à en boucle et vous ne souhaitez pas stocker toutes les données en mémoire.
Je souhaite remercier l'auteur de la démo 'audio_sample' qui m'a aidé à comprendre trouver beaucoup de réponses à mes questions sur la façon de jouer des échantillons sonores. En particulier, comment lier des échantillons à un exécutable.
Reportez vous au Tutorial Six pour plus d'information sur la façon de structurer son application pour jouer des sons.
Les mises à jour de ce tutorial peuvent être obtenues sur le weblog de l'auteur (Chris Double) : http://radio.weblogs.com/0102385. Il peut être contacté à l'adresse chris.double@double.co.nz
Copyright (c) 2005, Chris Double. Tous droits réservés. Traduction par AlekMaul (alekmaul@portabledev.com).



