Moteur de son utilisant fmod (c++) (wav-midi-mp3-etc...)

Soyez le premier à donner votre avis sur cette source.

Vue 13 368 fois - Téléchargée 816 fois

Description

Ce petit moteur sonore est vraiment le B-A BA de ce
que l'on peut faire avec Fmod.

Il est réalisé en C/C++ mais d'autres
langages aurais aussi bien pu faire l'affaire ...

Il va sagir d'initialiser/désinitialiser Fmod,
de pouvoir ajouter/retirer des sons stockés à l'intérieur
d'un vector (utilisé pour l'occasion comme un petit tableau dynamique),
et de pouvoir les jouer et les stopper quand on le désire.

Donc en résumé, tout ça restera très basique : pas de son 3D, pas d'effets ou de fioritures...
Ce tut est donc réservé aux débutants (dont je fais partie ;-)) désirant avoir une première approche évoluée de ce que peut apporter la prise en charge du son dans un prog...

J'ai réalisé ce SoundEngine sous Visual C++ 6, donc je ne peux
expliquer comment régler l'environnement que sur VS.

Mais la doc de Fmod étant très complète, je vous propose d'aller y jeter
un oeil si vous êtes allergiques à Microsoft, tout sera expliqué en détail pour les autres EDI.

Donc pour commencer vous n'aurez besoin de 3 choses :
- la librairie fmodvc.lib à inclure dans le projet (spécifique ici à VS6 !)
- le fichier d'entête fmod.h
- la dll de Fmod : fmod.dll à mettre soit dans le
répertoire de l'exécutable, soit dans system32
Vous trouverez tout ça en téléchargeant l'api Fmod sur le site de Fmod (http://www.fmod.org)

Après, c'est que du code, mais rien de bien méchant vous allez voir.

Source / Exemple :


1. Création d'une classe intermédiaire, la classe Son

INTERFACE

class Son 
{
      public:
      Son() {}
      ~Son() {}

      //format
      int format;
      FSOUND_STREAM * stream;
      FSOUND_SAMPLE * sample;
      FMUSIC_MODULE * module;

      //charge un fichier son
      BOOL Charger(char *fichAdr, int format);
      void Liberer(void);

      //joue et un stoppe le son
      BOOL Jouer(void);
      void Stopper(void);
};

Vous vous demandez sûrement quels sont les formats de sons évoqués ci-dessus. C'est tout simple. On peut disinguer 3 grandes sortes de sons :
- les gros fichiers WAV/MP3/etc... stockés en stream
- les petits fichiers WAV/MP3/etc... stockés en sample
- les fichiers MIDI/etc... stockés en modules

Remarque : c'est à l'utilisateur lui-même de savoir à l'avance quel format de fichier va être chargé

Remarque : un petit point important : Fmod est séparé en deux plus petites api, FSound et FMusic. Chacune est spécialisée dans le traitement d'une famille de son. FSound pour les stream et sample et FMusic pour les modules.

Pour chaque son il y a donc un pointeur vers chacune de ces structures. 

On définit pour cela quelques constantes qui vont être passées en argument lors du chargement des sons :

//gros mp3/wav -> loop
#define STREAM_TYPE    0
//petits mp3/wav -> joués une fois
#define SAMPLE_TYPE    1
//midi et autres -> loop
#define MODULE_TYPE_MUSIC    2
//midi et autres -> joués une fois
#define MODULE_TYPE_SOUND   3

Les autres fonctions parlant d'elles-même, passons à la réalisation.

Nous n'allons exposer ici que la méthode de chargement. Etant la plus "complexe" (^_^ toute proportion gardée), elle va nous permettre de poser quelques bases :

REALISATION

/* Charge un son en fonction du format spécifié*/
BOOL Son::Charger(char *fichAdr, int format)
{
      BOOL result = TRUE;

      //on stocke le format du son
      this->format = format;

      //on charge suivant le format
      switch(format)
      {
            //stream
            case STREAM_TYPE:
            stream = NULL;
            stream = FSOUND_Stream_OpenFile(fichAdr,                   FSOUND_LOOP_NORMAL, 0);
            if(stream == NULL)
                  result = FALSE;
            break;

            //sample
            case SAMPLE_TYPE:
            sample = NULL;
            sample =                   FSOUND_Sample_Load(FSOUND_FREE, fichAdr,             FSOUND_LOOP_OFF, 0);
            if(sample == NULL)
                  result = FALSE;
            break;

            //module loop
            case MODULE_TYPE_MUSIC:
            module = NULL;
            module = FMUSIC_LoadSong(fichAdr);
            FMUSIC_SetMasterVolume(module, 160);
            if(module == NULL)
                  result = FALSE;
            break;

            //module simple
            case MODULE_TYPE_SOUND:
            module = NULL;
            module = FMUSIC_LoadSong(fichAdr);
            FMUSIC_SetMasterVolume(module, 160);
            FMUSIC_SetLooping(module,FALSE);
            if(module == NULL)
                  result = FALSE;
            break;

            //si aucun init fail
            default:
            result = FALSE;
            break;
      }

      return result;
}

Remarque : j'ai choisi de régler du volume des sons de type module juste après leur chargement. 
Il faut savoir que pour ce type de fichiers particulier, le volume se règle individuellement pour chaque module (de 0 à 255 - 160 étant un bon compromis).
On aurais aussi bien pu régler tout ça par la suite, mais autant le faire depuis le début.... 

Ce qu'il faut bien remarquer dans cette méthode est le passage en paramètre du format du son. Comme je l'ai évoqué plus haut, c'est donc à l'utilisateur de savoir quel sont il va charger au moment de l'appel de la procédure.

J'ai également fait un choix (contestable, mais se révèlant après expérience plutôt concluant) : les répétitions.
C'est-à-dire le nombre de fois qu'un son va être joué d'affilé : les stream et les modules de type musique sont répétées à l'infini, tandis que les sample et les modules de type son ne sont joués qu'une fois.
On aurais très bien pu complexifier cette fonction de chargement pour la rendre plus "customisable"... Mais le principal caractère de ce moteur étant son accessibilité, je n'ai rien inplémenté de tel.

Excepté ces petits détails, aucune difficulté (consultez la doc de Fmod pour les méthodes de chargement (d'ailleurs très simples à utiliser)).

Le reste des méthodes, Jouer/Stopper et Liberer ne necessitent pas selon moi de longues explications si l'on à compris comment était réalisée la méthode Charger.

A noter tout de même que la fonction Charger ne libére rien au moment de charger un son. Pensez donc bien à libérer un son avant de vouloir le recharger à partir d'un autre fichier.

Voilà les autres fonction.

/* Libère le son*/
void Son::Liberer(void)
{
      //si c'est une stream ou un module on libère
      if(format == STREAM_TYPE)
      {
            FSOUND_Stream_Close(stream);
            stream=NULL;
      }
      else if(format == MODULE_TYPE_MUSIC 
             || format == MODULE_TYPE_SOUND)
      {
            FMUSIC_FreeSong(module);
            module=NULL;
      }
}

/* Joue le son en fonction du format*/
BOOL Son::Jouer(void)
{
      //on joue suivant le type de format
      switch(format)
      {
            //stream (loop)
            case STREAM_TYPE:
            if(FSOUND_Stream_Play(FSOUND_FREE,                                                       stream) == -1)
                  return FALSE;
            break;

            //sample
            case SAMPLE_TYPE:
            if(FSOUND_PlaySound(FSOUND_FREE,                                                       sample) == -1)
                  return FALSE;
            break;

            //module (loop)
            case MODULE_TYPE_MUSIC:
            if(FMUSIC_PlaySong(module) == NULL)
                  return FALSE;
            break;

            //module
            case MODULE_TYPE_SOUND:
            if(FMUSIC_PlaySong(module) == NULL)
                  return FALSE;
            break;

            default:
            break;
      }

      return TRUE;
}

/* Stope la lecture en cours d'une stream ou d'un module*/
void Son::Stopper(void)
{
      //si c'est une stream ou un module on stoppe
      if(format == STREAM_TYPE)
            FSOUND_Stream_Stop(stream);
      else if(format == MODULE_TYPE_MUSIC
              ||format == MODULE_TYPE_SOUND)
            FMUSIC_StopSong(module);
} 

2. La classe SoundEngine

Voilà comment elle se présente :

INTERFACE

class SoundEngine
{
      public :
      vector<Son> sons;

      SoundEngine() {}
      ~SoundEngine() {}

      /* Initialise tout : 

  • mixRate = frequence de sortie
  • maxChannels = nombre de sons pouvant être traité à la fois
  • /
BOOL Initialiser(int mixRate, int maxChannels); //On désinitialise tout BOOL Desinitialiser(void); /* Charge un nouveau son :
  • fichAdr = adresse du fichier son
  • format = stream/sample/module (voir constantes)
  • /
BOOL AjouterSon(Son son); BOOL AjouterSon(Son son, int index); BOOL AjouterSon(char *fichAdr, int format); BOOL AjouterSon(char *fichAdr, int format, int index); //Retire un son, soit à un index donné, soit le dernier chargé BOOL RetirerSon(void); BOOL RetirerSon(int index); //Joue le son BOOL Jouer(int index); //Stoppe le son BOOL Stopper(int index); }; On peut distinguer 3 types de méthodes : - Initialisation : Initialiser/Desinitialiser - Stockage : AjouterSon/RetirerSon - Rendu : Jouer/Stopper Pour stocker les sons j'utilise un vector de Son Pour ceux qui ne savent pas ce que c'est qu'un vector, c'est en fait une fonctionnalité faisant partie de la stl - bibliothèque de classes container - permettant en autres de créer de tableaux dynamique efficacement. Il permet d'éviter l'enchainement des new et delete en cascade et ainsi pas mal de problèmes ^_^. Allez faire un tour sur la page des liens si vous voulez en savoir plus. REALISATION Bon, voilà le code : /*Initialisation*/ BOOL SoundEngine::Initialiser(int mixRate, int maxChannels) { //On definit le volume des mp3 et wave FSOUND_SetVolume(FSOUND_ALL,160); return FSOUND_Init(mixRate, maxChannels, FSOUND_INIT_USEDEFAULTMIDISYNTH ); } Remarque : on règle ici le volume des stream et des sample (map3 et wav principalement). Le volume de ces format ne sont pas réglés individuellement pour chaque son, comme pour les modules, mais globalement. A noter que cette fonctionalité est utilisable à n'importe quel moment dans un programme. /*Desinitialisation*/ BOOL SoundEngine::Desinitialiser(void) { BOOL init = TRUE; try { //on stoppe et libère les sons for(int i=0; i<sons.size(); i++) { sons.at(i).Stopper(); sons.at(i).Liberer(); } } catch(...) { init = FALSE; } //on ferme Fmod FSOUND_Close(); return init; } Remarque : attention à ne pas l'oublier, la bonne libération des objets peut éviter bien des soucis par la suite (surtout sous Win9x...) /*Ajoute un son dans le vector - Ver1*/ BOOL SoundEngine::AjouterSon(Son son) { //on le range dans le vector sons.push_back(son); return TRUE; } /*Ajoute un son dans le vector - Ver2*/ BOOL SoundEngine::AjouterSon(Son son, int index) { //si l'index dépasse la capacité du vector if(index < 0 || index > sons.size() - 1) return FALSE; vector<Son>::iterator it; it = sons.begin(); it += index; //on le range dans le vector à l'index indiqué sons.insert(it, son); return TRUE; } /*Ajoute un son dans le vector - Ver3*/ BOOL SoundEngine::AjouterSon(char *fichAdr, int format) { Son temp; if(!temp.Charger(fichAdr, format)) return FALSE; //on le range dans le vector sons.push_back(temp); return TRUE; } /*Ajoute un son dans le vector - Ver4*/ BOOL SoundEngine::AjouterSon(char *fichAdr, int format, int index) { Son temp; //si l'index dépasse la capacité du vector if(index < 0 || index > sons.size() - 1) return FALSE; //on charge un objet temporaire if(!temp.Charger(fichAdr, format)) return FALSE; vector<Son>::iterator it; it = sons.begin(); it += index; //on le range dans le vector à l'index indiqué sons.insert(it, temp); return TRUE; } Remarque : 4 versions pour une même méthode. On peut ainsi ajouter des sons directement à partir d'une classe Son mais également en fournissant simplement le chemin d'accès au fichier ainsi que son format. Il est également possible de choisir où ce son sera stocké dans le vector. /*Retire un son du vector - Ver1*/ BOOL SoundEngine::RetirerSon(void) { //on retire le dernier son chargé sons.pop_back(); return TRUE; } /*Retire un son du vector - Ver2*/ BOOL SoundEngine::RetirerSon(int index) { //si l'index dépasse la capacité du vector if(index > sons.size() - 1 || index < 0) return FALSE; vector<Son>::iterator it; it = sons.begin(); it += index; //on retire le son à l'index indiqué sons.erase(it); return TRUE; } Remarque : 2 versions pour cette méthode. Cela va permettre de pouvoir retirer un son à n'importe quel endroit du vector. /*Joue le son situé à l'index indiqué*/ BOOL SoundEngine::Jouer(int index) { //si l'index dépasse la capacité du vector if(index > sons.size() - 1 || index < 0) return FALSE; sons[index].Jouer(); return TRUE; } /*Stoppe le son indiqué*/ BOOL SoundEngine::Stopper(int index) { //si l'index dépasse la capacité du vector if(index > sons.size() - 1 || index < 0) return FALSE; //on joue le son sons[index].Stopper(); return TRUE; } Remarque : Pour jouer et stopper les sons, on utilise directement la fonction contenue dans la classe Son, après avoir bien sur testé la validité de l'index fournis en paramètre. Voilà, c'est tout pour la création du SoundEngine. Si vous avez des questions n'hésitez pas à me contacter. Passons maintenant à son utilisation au sein de votre programme. 3. Utilisation Tout d'abord, vous devez déclarer une variable de type SoundEngine (ici globale mais on peut adapter). //variable de type SoundEngine SoundEngine soundEngine; //Viens ensuite l'initialisation : //fréquence à 44khz et 32 cannaux soundEngine.Initialiser(44000, 32); //On peut maintant ajouter des sons : //ajout de sons avec une classe Son déjà déclarée Son temp; temp.Charger("grosMP3.mp3", TYPE_STREAM); soundEngine.AjouterSon(temp); temp.Liberer(); temp.Charger("petitWAV.wav", TYPE_SAMPLE); soundEngine.AjouterSon(temp, 1); temp.Liberer(); temp.Charger("midiMusique1.mid", MODULE_TYPE_MUSIC); soundEngine.AjouterSon(temp); temp.Liberer(); temp.Charger("midiSon1.mid", MODULE_TYPE_SOUND); soundEngine.AjouterSon(temp); temp.Liberer(); //ajout de sons par chemin d'accés soundEngine.AjouterSon("petitMP3.mp3", SAMPLE_TYPE); soundEngine.AjouterSon("grosWAV.wav", STREAM_TYPE); soundEngine.AjouterSon("midiMusique1.mid", MODULE_TYPE_MUSIC); soundEngine.AjouterSon("midiSon1.mid", MODULE_TYPE_SOUND, 0); //Par exemple. //On peut maintenant les jouer/stopper. ... //jouera midiSon1.mid soundEngine.Jouer(0); //jouera grosMP3.mp3 soundEngine.Jouer(1); ... //arretera la lecture soundEngine.Stopper(0); //arretera la lecture soundEngine.Stopper(1); ... //jouera grosWAV.wav soundEngine.Jouer(6); ... //arretera la lecture soundEngine.Stopper(6); //A la fin de votre programme il ne vous restera plus qu'à désinitialiser : //désinitialise Fmod et libère tous les sons chargés soundEngine.Desinitialiser(); Voilà c'est tout ! Si vous voyez des erreurs, prévenez-moi je corrigerais tout ça dès que je pourrais. Si vous avez des conseils concernant ce SoundEngine, n'hésitez pas non plus à me contacter ;-). J'ai fournit une archive contenat le fichier d'entête et de réalisation. Bonne lecture. Fab

Codes Sources

A voir également

Ajouter un commentaire

Commentaires

cs_LordBob
Messages postés
2865
Date d'inscription
samedi 2 novembre 2002
Statut
Membre
Dernière intervention
11 mai 2009
8 -
je trouve que ta source c'est une bonne idée, elle a un petit aspect utile et surtout elle permet de bien apprendre a se servir de FMOD !!!
mais un petit truc que tu pourrais faire, sur le site, dans la partie source, tu fait une sorte d'explication de ta source, je pense que ca serait bien si tu l'inserais dans ton zip...
voila sinon continue ta source, rend la plus puissance et continue a posté les mises a jour sur cppfrance...
cs_djl
Messages postés
3011
Date d'inscription
jeudi 26 septembre 2002
Statut
Membre
Dernière intervention
27 novembre 2004
5 -
et change aussi qq truc pour la rendre plus c++: utilise bool plutot que BOOL, ne met pas void en parametre à une fonction sans parametre (ca concerne juste le c pour differencier prototype et declaration), remplace tes #define par une enum pour tes constante...
tu peux aussi utiliser 0 à la place de NULL
Funto66
Messages postés
1267
Date d'inscription
mercredi 1 janvier 2003
Statut
Membre
Dernière intervention
28 février 2007
3 -
Elle est sympa ta source ;)
djl >> là je suis pas d'accord, faut laisser NULL au lieu de 0, c'est plus explicite, ça revient au même et c'est défini dans la bibliothèque standard.
Par contre je suis d'accord pour remplacer les BOOL par des bool ^^

Aussi, j'ai pas trop regardé mais il me semble que l'on est obligés d'appeler les fonctions son.Liberer() avant un son.Charger() et qu'on doit aussi appeler engine.Desinitialiser().
C'est là que tu pourrais profiter de la puissance du C++ : il faudrait créer des destructeurs qui appelleraient ces méthodes, et ne pas être obligé de libérer un son avant d'en charger un autre : pour ça, la fonction Liberer() serait appelée dans Son::Charger(), seulement si un son était déjà chargé auparavant bien sûr ^^

Voilà, bonne prog.
cs_djl
Messages postés
3011
Date d'inscription
jeudi 26 septembre 2002
Statut
Membre
Dernière intervention
27 novembre 2004
5 -
ok, mais NULL n'est pas defini dans la lib standard du c++ mais dans celle du c

"In C++, a literal zero is most appropriate for use as a null pointer
constant" (cpptips)

et stroustrup dit : "If you feel you must define NULL, use const int NULL = 0;"
et pas #define NULL ((void *)0)
Funto66
Messages postés
1267
Date d'inscription
mercredi 1 janvier 2003
Statut
Membre
Dernière intervention
28 février 2007
3 -
Oui c'est vrai c'est dans la lib du C.
Mais dans tous les cas je reste persuadé qu'il faut utiliser NULL si on veut que le code soit clair.
En fait, pour l'idée de Stroustrup, faudrait faire un
#ifdef NULL
#undef NULL
extern const int NULL = 0;
#endif

et dans un fichier *.cpp :
const int NULL = 0;

Personnellement, je ne vois pas trop ce qu'il y a de mal à utiliser le NULL de base...j'ai jamais eu de problème avec.

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.