Jouer des sons Wave avec C++ Builder 6

Gilles Louïse

Ce tutoriel explique la façon de jouer tous les fichiers Wave à partir d'un répertoire de base en utilisant le composant MediaPlayer de C++ Builder 6.

- Titre : Jouer des sons Wave avec C++ Builder 6

- Auteur : Gilles Louïse

- Parution : 1 novembre 2002

- Mise à jour : 11 mars 2013

- Licence : Copyright ® 2013 Gilles Louïse. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Explication

- II. La classe principale

- III. Initialisation

- IV. La procédure AllRep

- V. La procédure CreateSerie

- VI. La procédure SousRep

- VII. La procédure AjusteSerie

- VIII. La fonction ExisteWave

- IX. La fonction ExisteRep

- X. La fonction ExisteIntegral

- XI. L'événement OnClick du bouton Joue

- XII. La fonction Correct

- XIII. La procédure JoueOrdre

- XIV. La fonction PosIntegral

- XV. La fonction JoueRep

- XVI. La procédure Ajoute

- XVII. La fonction JoueTout

- XVIII. La fonction JoueUnite

- XIX. La procédure JoueAlea

- XX. L'événement OnDestroy

- XXI. Pour faire fonctionner ce tutoriel

I - Explication

Il existe dans le commerce un certain nombre de logiciels d'apprentissage de langues étrangères. Pour certains d'entre eux, vous n'avez pas accès aux fichiers son, on ne peut donc rien faire. Mais pour d'autres, chaque phrase est un fichier Wave numéroté et situé dans un répertoire précis du CR-ROM. C'est le cas par exemple du logiciel « Talk to me ». Or, il manque à ces logiciels une fonction toute simple, celle de lire les phrases les unes à la suite des autres sans aucune participation de l'utilisateur. Ce tutoriel donc pallie cette carence. Nous allons copier sur notre disque dur à partir de CD-ROM tous les répertoires et sous-répertoires où se trouvent ces phrases qui sont de petits fichiers Wave pour chacune d'elles et nous allons créer cette fonction de lecture automatique de ces fichiers son. Ainsi l'utilisateur n'a qu'à écouter les phrases les unes après les autres sans avoir à cliquer quoi que ce soit pour passer à la phrase suivante.

Dans le logiciel « Talk to me », il existe six séries en fonction de diverses situations, chaque série contient trois types de répertoire, le répertoire Question qui regroupe les questions que pose l'ordinateur, le répertoire Réponse qui contient les réponses possibles et le répertoire Locution qui renferme un certain nombre d'expressions idiomatiques de la langue étudiée. Cela dit, pour rendre notre tutoriel un peu plus universel, nous n'allons pas écrire « en dur » tous ces noms de répertoires et sous-répertoires bien que connus à l'avance, mais nous allons au contraire calculer à partir d'un répertoire de base donné tous les sous-répertoires existants et rechercher tous les fichiers Wave susceptibles de se trouver dans l'arborescence. Ainsi, si vous voulez utiliser cette fonction pour tout autre chose, notamment faire jouer de la musique en continu, ça marchera immédiatement. Nous n'écrirons « en dur » que le répertoire de base dans la constante AnsiString RepBase.

RepBase

1. #include< vcl.h>

3. #include< Math.hpp>

5. #pragma hdrstop

7. #include "SonWave.h"

9. //---------------------------------------------------------------------------

11. #pragma package(smart_init)

13. #pragma resource "*.dfm"

15. TWave *Wave;

17. const AnsiString RepBase="E:\\Allemand";

19. const int MaxDecal=100;

La variable RepBase (ligne 17) contient le nom du répertoire de base enfermant tous les sous-répertoires. Si vous voulez entendre la totalité des sons Wave de votre disque dur C principal, il suffira d'écrire const AnsiString RepBase="C:"; mais vous devrez attendre quelques secondes voire quelques dizaines de secondes en fonction de la complexité de votre configuration, le temps que le programme décèle la totalité des sous-répertoires présents. Le mieux serait malgré tout d'installer les fichiers Wave que vous voulez entendre dans un répertoire prévu à cet effet avec ou sans sous-répertoires et de renseigner correctement la variable RepBase.

Remarquez que notre forme principale s'appelle TWave, elle contient quatre composants :

un bouton ayant pour nom Joue (propriété Name) ;

un TlabeledEdit ayant pour nom DepartMode (propriété Name) ;

un label ayant pour nom FichierWave (propriété Name) ;

un MediaPlayer ayant pour nom Media (propriété Name).

Cela dit, le MediaPlayer n'a pas besoin d'être visible, car nous allons n'utiliser que la fonction Play, donc nous mettons sa propriété Visible à false dans l'inspecteur d'objets.

Le TLabeledEdit (composant nouveau propre à C++ Builder 6) servira à indiquer la série de départ ou encore le mode aléatoire. En effet, à partir du répertoire de base, le programme va calculer le nombre de séries c'est-à-dire le nombre de sous-répertoires situés dans le répertoire de base. Par exemple, dans le cas du logiciel « Talk to me », le programme trouvera qu'il y a six séries soit six sous-répertoires que nous nommerons par exemple SERIE1, SERIE2, etc. jusqu'à SERIE6. Cela permettra à l'utilisateur de choisir sa série de départ, il suffira alors d'entrer un nombre entier entre 1 et 6 dans DepartMode (TLabeledEdit). Mais nous allons aussi offrir à l'utilisateur la possibilité d'écouter les fichiers Wave en aléatoire. Dans ce cas, on va mémoriser la totalité des noms de fichiers Wave trouvés dans l'arborescence dans un TStringList puis on va choisir un nombre aléatoire compris entre 0 et Count-1 et faire jouer le fichier correspondant à l'indice choisi au hasard. Pour activer le mode aléatoire, il faudra simplement écrire la lettre A dans le composant DepartMode (TLabeledEdit). Donc si n séries sont trouvées c'est-à-dire s'il y a n sous-répertoires dans le répertoire de base, l'utilisateur devra entrer un nombre entier compris entre 1 et n bornes incluses ou bien la lettre A pour forcer le mode aléatoire. On utilisera pour ce faire la fonction RandomRange qui renvoie un entier aléatoire inclus entre deux nombres bornes incluses, ce qui explique que Math.hpp fasse partie des « include » en début de programme (#include <Math.hpp>).

Quant au composant FichierWave (TLabel), il affichera simplement le nom du fichier exécuté précédé du chemin d'accès. Comme le MediaPlayer est invisible, la présentation aura en gros l'apparence suivante à l'exécution.

II - La classe principale

Voici notre classe principale avec les déclarations utilisateur, variables et méthodes.

Classe principale

1. class TWave : public TForm

3. {

5. __published: // Composants gérés par l'EDI

7. TButton *Joue;

9. TMediaPlayer *Media;

11. TLabel *FichierWave;

13. TLabeledEdit *DepartMode;

15. void __fastcall FormDestroy(TObject *Sender);

17. void __fastcall JoueClick(TObject *Sender);

19. private: // Déclarations de l'utilisateur

21. public: // Déclarations de l'utilisateur

23. __fastcall TWave(TComponent* Owner);

25. TStringList *ListeSons;

27. TStringList *Serie;

29. TStringList *Integral;

31. void __fastcall JoueOrdre();

33. void __fastcall JoueAlea();

35. bool __fastcall JoueTout();

37. bool __fastcall JoueRep(AnsiString);

39. void __fastcall Ajoute(AnsiString);

41. int __fastcall JoueUnite(int);

43. bool __fastcall Correct();

45. void __fastcall AllRep();

47. void __fastcall CreateSerie();

49. void __fastcall SousRep(AnsiString);

51. bool __fastcall ExisteRep(AnsiString);

53. int __fastcall PosIntegral(AnsiString);

55. bool __fastcall ExisteWave(AnsiString);

57. void __fastcall AjusteSerie();

59. bool __fastcall ExisteInIntegral(AnsiString);

61. };

Nous déclarons trois TStringList : Serie qui contiendra les séries valides c'est-à-dire les sous-répertoires contenus dans RepBase et contenant au moins un fichier Wave ; Integral qui contiendra l'intégralité des sous-répertoires valides à partir de la base et ListeSons qui contiendra tous les fichiers Wave susceptibles d'être joués. Dans le cas où l'on jouera ces fichiers dans l'ordre à partir d'un point de départ, on initialisera pour chaque série ListeSons et on jouera le répertoire en entier allant des indices 0 à ListeSons->Count-1 et à chaque changement de série on recalculera ListeSons. Dans le cas d'une exécution en aléatoire (DepartMode="A"), on mémorisera dans ListeSons tous les fichiers Wave trouvés dans l'arborescence et on jouera au hasard à chaque itération un fichier dont l'indice sera compris entre 0 et ListeSons->Count-1 bornes incluses.

III - Initialisation

__fastcall TWave::TWave(TComponent* Owner)

: TForm(Owner)

{

ListeSons = new TStringList;

Serie = new TStringList;

Integral = new TStringList;

DepartMode->Text="1";

AllRep

();
}

On instancie donc nos trois TStringList. On propose « 1 » comme point de départ par défaut c'est-à-dire que par défaut le programme jouera dans l'ordre tous les fichiers trouvés à partir du premier sous-répertoire du répertoire de base. Puis on appelle la procédure AllRep qui va calculer toutes les séries à partir du répertoire de base et l'intégralité des répertoires utiles, cette fonction initialise donc les deux TStringList Serie et Integral.

IV - La procédure AllRep

void __fastcall TWave::AllRep()

{

int i;

AnsiString M;

CreateSerie();

for(i=0;i<Serie->Count;i++) SousRep(Serie->Strings[i]);

AjusteSerie();

if(!Serie->Count)

M="Aucun fichier Wave trouvé à partir du répertoire de base";

if(Serie->Count==1)

M="Entrez 1 ou A";

if(Serie->Count>1)

M="Entrez un nombre entre 1 et "+IntToStr(Serie->Count)+" ou A";

DepartMode->ShowHint=true;

DepartMode->Hint=M;

}

On commence par créer les séries de base c'est-à-dire les répertoires situés dans le répertoire de base (CreateSerie();). Ensuite, pour chacune de ces séries ou sous-répertoires du répertoire de base (for(i=0;i<Serie->Count;i++)), on calcule tous les sous-répertoires qu'on mémorisera dans Integral (SousRep(Serie->Strings[i]);), la TStringList Integral contient donc tous les sous-répertoires de l'arborescence contenant au moins un Wave. Ensuite on ajuste les séries (AjusteSerie();) en ne maintenant dans la liste que les occurrences valides c'est-à-dire celles contenant au moins un fichier Wave dans l'arborescence. Ainsi, après ce calcul on sait qu'il y aura Serie->Count séries valides et Integral->Count répertoires contenant au moins un fichier Wave. Ensuite on prépare dans un AnsiString un message de bulle d'aide (hint) pour le TLabeledEdit DepartMode qui dira les diverses possibilités de l'utilisateur. On distingue trois cas. Premier cas, si Serie->Count est nul (if(!Serie->Count)), aucun fichier Wave n'a été détecté dans l'arborescence (M="Aucun fichier Wave trouvé à partir du répertoire de base";). Deuxième cas, si Serie->Count est égal à 1, il n'y a alors qu'une seule série, ce serait le cas par exemple où l'utilisateur copierait tous ses fichiers Wave dans le répertoire de base, dans ce cas on ne peut choisir que 1 ou A (M="Entrez 1 ou A";). Troisième cas, si Serie->Count est supérieur à 1, l'utilisateur peut entrer un nombre compris entre 1 et Serie->Count ou A (M="Entrez un nombre entre 1 et "+IntToStr(Serie->Count)+" ou A";). Le message de hint (bulle d'aide) est dans M, on met à true la propriété ShowHint pour que la bulle d'aide s'affiche quand le curseur se stabilise sur le composant (DepartMode->ShowHint=true;), on aurait également pu aussi mettre cette propriété à true directement dans l'inspecteur d'objets et donc ne pas le faire par programme. Puis on termine en indiquant au composant le message de hint (DepartMode->Hint=M;).

V - La procédure CreateSerie

void __fastcall TWave::CreateSerie()

{

AnsiString NomRep;

TSearchRec SRec;

bool REP;

if(ExisteWave(RepBase)) Serie->Add(RepBase);

if (!FindFirst(RepBase+"\\*.*", faAnyFile, SRec))

do

{

NomRep=SRec.Name;

REP=SRec.Attr& faDirectory;

if(REP& NomRep[1]!='.') Serie->Add(RepBase + "\\" + NomRep);

}

while(!FindNext(SRec));

FindClose(SRec);

}

On déclare trois variables, un AnsiString qui contiendra le nom de répertoires (AnsiString NomRep;), une structure de type TSearchRec (TSearchRec SRec;) utilisée dans les fonctions de recherche FindFirst, FindNext et FindClose et un booléen (bool REP;) pour tester l'attribut du nom trouvé, lequel dira s'il s'agit d'un répertoire ou non. Si le répertoire de base contient au moins un fichier Wave (if(ExisteWave(RepBase))) on ajoute ce répertoire à la série (Serie->Add(RepBase);). C'est une précaution nécessaire pour le cas où il y aurait effectivement des fichiers Wave dans ce répertoire. Si par exemple un utilisateur souhaite ne mettre des fichiers Wave que dans ce répertoire de base, notre utilitaire marchera correctement, on ne jouera alors que ce que contient ce répertoire. Si un nom de fichier au moins est trouvé (if (!FindFirst(RepBase+"\\*.*", faAnyFile, SRec))), on entre dans une boucle en do pour les scruter tous et traiter les sous-répertoires. À chaque occurrence, on lit le nom du fichier (NomRep=SRec.Name;), on positionne le booléen REP en fonction du fait que ce nom est celui d'un répertoire (REP=SRec.Attr & faDirectory;). Cette instruction exécute un ET logique entre les attributs du fichier SRec.Attr et la constante faDirectory. Cela revient à lire le bit correspondant à cette information binaire (répertoire ou non), si le bit lu et isolé par le ET logique est non nul, le nom de fichier est un nom de répertoire et on aura REP=true (true=non nul) sinon on obtient une valeur nulle et on aura REP=false (false=nul). Si REP est vrai (i.e. si le nom lu correspond à un nom de répertoire) et si ce nom ne commence pas par un point (if(REP & NomRep[1]!='.')) — on exclut ainsi les fichiers système notamment les deux répertoires "."et ".." — on ajoute ce nom à la série par concaténation du répertoire de base et du nom trouvé (Serie->Add(RepBase + "\\" + NomRep);) et la boucle se poursuit tant qu'il y a un autre nom à analyser (while(!FindNext(SRec));). Au sortir de cette boucle, la TStringList Série est initialisée, on restitue la mémoire utilisée par la recherche (FindClose(SRec);).

VI - La procédure SousRep

void __fastcall TWave::SousRep(AnsiString Rep)

{

AnsiString NomRep;

TSearchRec SRec;

bool REP;

if(ExisteWave(Rep)) Integral->Add(Rep);

if(!ExisteRep(Rep)) return;

FindFirst(Rep+"\\*.*", faAnyFile, SRec);

do

{

NomRep=SRec.Name;

REP=SRec.Attr& faDirectory;

if(REP& NomRep[1]!='.') SousRep(Rep+"\\"+NomRep);

}

while(!FindNext(SRec));

FindClose(SRec);

}

C'est une procédure récursive c'est-à-dire qu'elle s'appelle elle-même si un sous-répertoire doit être analysé à son tour. Elle reçoit en argument l'AnsiString Rep qui est le répertoire à analyser. On commence par ajouter ce répertoire à l'intégral s'il y a au moins un fichier Wave détecté dans ce répertoire en tant que tel (if(ExisteWave(Rep)) Integral->Add(Rep);). S'il n'y a pas de sous-répertoire dans Rep (if(!ExisteRep(Rep))), on retourne à l'appelant, car il n'y a rien d'autre à faire (return;). Il faut se souvenir que dans une procédure récursive, il faut toujours tester en priorité le retour possible avant le réappel ou l'autoappel de la procédure sinon il y aurait une boucle infinie et donc débordement de la pile et erreur d'exécution. Une procédure récursive doit donc soit commencer par un if pour tester en premier lieu la condition de retour, soit être du type while pour offrir la possibilité de ne pas exécuter le corps de la boucle et donc retourner immédiatement à l'appelant sans autoappel. Si au moins un répertoire est détecté, on lit le premier nom de fichier (FindFirst(Rep+"\\*.*", faAnyFile, SRec);). Ici, on ne teste pas la valeur booléenne retournée par la fonction FindFirst, car un nom existe obligatoirement du fait qu'un répertoire vient d'être détecté, ce pour quoi nous entrons dans cette section de programme. Ensuite, on entre dans une boucle en do, on lit le nom de fichier (NomRep=SRec.Name;), on détecte si oui ou non c'est un sous-répertoire (REP=SRec.Attr& faDirectory;). Si c'est un répertoire dont le nom ne commence pas par un point (if(REP & NomRep[1]!='.')), on s'appelle soi-même en analysant ce sous-répertoire (SousRep(Rep+"\\"+NomRep);). C'est grâce à cette récursivité qu'on est certain d'analyser la totalité de l'arborescence. La boucle continue tant qu'il y a des noms de fichier à lire (while(!FindNext(SRec));). Puis on restitue la mémoire utilisée par les fonctions liées à la recherche (FindClose(SRec);).

VII - La procédure AjusteSerie

void __fastcall TWave::AjusteSerie()

{

int i=0;

while(i!=Serie->Count)

if(ExisteInIntegral(Serie->Strings[i])) i++; else Serie->Delete(i);

}

Après avoir renseigné la TSringList Integral qui contient l'intégralité des sous-répertoires concernés c'est-à-dire de ceux qui ont au moins un fichier Wave à jouer, il convient d'ajuster la série de base (TStringList Serie), ce qui clôture la procédure AllRep. En effet, pour que cette série soit valide, il faut que chaque chaîne Serie->Strings[] existe dans au moins un répertoire Integral->Strings[] sinon l'occurrence Serie->Strings[] sera supprimée de la liste, car cela signifie qu'aucun sous-répertoire dans l'arborescence ne contient de fichier Wave. On part de l'indice 0 qui est le premier d'une TStringList (int i=0;). Tant que i n'est pas égal au nombre de séries c'est-à-dire tant que toutes les séries n'ont pas été analysées (while(i!=Serie->Count)), on regarde si la série d'indice i existe dans la TStringList Integral (if(ExisteInIntegral(Serie->Strings[i]))). Si c'est le cas, la série est correcte, car il y a au moins un fichier Wave dans l'arborescence, on la garde donc dans la TStringList et on se contente de passer à l'indice suivant (i++;) sinon on supprime la série de la liste (else Serie->Delete(i);). Notez qu'en cas de suppression, i n'est pas incrémenté pour le test de l'occurrence suivante, mais la chaîne d'indice i représente bien la suivante après suppression par Delete du fait que les chaînes suivantes sont toutes remontées d'un indice. Après exécution de la procédure, on est certain d'avoir Serie->Count séries correctes.

VIII - La fonction ExisteWave

bool __fastcall TWave::ExisteWave(AnsiString Rep)

{

TSearchRec SRec;

bool OK;

OK=!FindFirst(Rep+"\\*.wav", faAnyFile, SRec);

FindClose(SRec);

return(OK);

}

Cette fonction reçoit l'AnsiString Rep en argument. Il s'agit de savoir si oui ou non il y a au moins un Wave dans le répertoire Rep. On positionne un booléen OK en fonction de l'existence d'un fichier d'extension wav dans Rep (OK=!FindFirst(Rep+"\\*.wav", faAnyFile, SRec);). On restitue la mémoire utilisée (FindClose(SRec);) et on renvoie à l'appelant la réponse située dans la variable booléenne OK (return(OK);).

IX - La fonction ExisteRep

bool __fastcall TWave::ExisteRep(AnsiString Rep)

{

WideString NomRep;

TSearchRec SRec;

bool REP=false;

if (!FindFirst(Rep+"\\*.*", faAnyFile, SRec))

do

{

NomRep=SRec.Name;

REP=(SRec.Attr& faDirectory) && (NomRep[1]!='.');

}

while(!FindNext(SRec)&& !REP);

FindClose(SRec);

return(REP);

}

Cette fonction booléenne est très proche de la précédente, on reçoit un nom de répertoire en argument et on cherche à savoir si ce répertoire contient au moins un sous-répertoire. On commence par mettre une variable booléenne REP à false (bool REP=false;). En effet, REP sera renvoyé à l'appelant, si le répertoire envoyé en argument est totalement vide, on n'entrera alors pas du tout dans la boucle et il faut renvoyer false dans ce cas, d'où la nécessité de cette initialisation. Si au moins un fichier se trouve dans le répertoire (if (!FindFirst(Rep+"\\*.*", faAnyFile, SRec))), on entre dans une boucle en do. À chaque occurrence, on lit le nom du fichier trouvé (NomRep=SRec.Name;) et on positionne REP en fonction du fait d'une part, qu'il s'agit d'un nom de répertoire et d'autre part, que ce nom ne commence pas par un point (REP=(SRec.Attr& faDirectory) && (NomRep[1]!='.');). La boucle continue tant qu'un fichier suivant existe et que REP est faux (while(!FindNext(SRec) && !REP);), donc si REP passe à true, la boucle s'arrête et on renverra true à l'appelant sinon, si REP reste systématiquement à false, c'est la fonction FindNext qui imposera l'arrêt de la boucle et on renverra false. Au sortir de la boucle, on restitue la mémoire utilisée (FindClose(SRec);) et on renvoie le booléen REP qui contient la réponse (return(REP);).

X - La fonction ExisteIntegral

bool __fastcall TWave::ExisteInIntegral(AnsiString S)

{

int i;

AnsiString A;

for(i=0;i<Integral->Count;i++)

{

A=Integral->Strings[i];

if(A.AnsiPos(S)==1) return(true);

}

return(false);

}

Cette fonction est appelée au moment de l'ajustement de la série (AjusteSerie). On reçoit en argument une série c'est-à-dire une chaîne Serie->Strings[i] parmi les Serie->Count existantes et on cherche à savoir si parmi les Integral->Count sous-répertoires valides, il y en a au moins un qui commence par la série S envoyée en argument. Si oui, ce sera la preuve que la série est valide, sinon la série n'est pas valide, car aucun sous-répertoire ne contient de Wave et l'appelant décidera au retour de supprimer cette série de la liste. Pour tous les indices possibles de la TStringList Integral (for(i=0;i<Integral->Count;i++)), on lit dans un AnsiString A le nom du répertoire (A=Integral->Strings[i];). Si la chaîne S se trouve en première position (if(A.AnsiPos(S)==1)), la série est valide et on retourne à l'appelant avec true (return(true);). Si la boucle se termine, c'est qu'aucune occurrence de la TStringList Integral ne commence par S (sinon le retour avec true aurait eu lieu), on en conclut que la série n'est pas valide et on retourne avec false (return(false);).

XI - L'événement OnClick du bouton Joue

void __fastcall TWave::JoueClick(TObject *Sender)

{

if(!Integral->Count)

{

AnsiString M="Aucun fichier Wave détecté à partir de "+RepBase;

ShowMessage(M);

return;

}

if(!Correct()) return;

if(DepartMode->Text=="A") JoueAlea(); else JoueOrdre();

}

L'événement OnClick du bouton Joue vérifie d'abord qu'un fichier Wave au moins a été trouvé, donc qu'il existe au moins un répertoire dans la TStringList Integral. Si le nombre de répertoires est nul (if(!Integral->Count)), on prépare un message dans un AnsiString (AnsiString M="Aucun fichier Wave détecté à partir de "+RepBase;), on affiche ce message (ShowMessage(M);) et on retourne à l'appelant (return;), il ne se passe donc rien dans ce cas. On continue donc s'il y a au moins un répertoire valide. Si ce qui est entré dans le TLabeledEdit DepartMode n'est pas correct (if(!Correct()) return;), on ne fait rien non plus (return;). Sinon, s'il y a au moins un répertoire valide et si ce qui est saisi dans DepartMode est correct, on cherche à savoir si l'utilisateur a entré la lettre A (if(DepartMode->Text=="A")). Si c'est le cas, on joue les fichiers Wave en mode aléatoire (JoueAlea();) sinon on les joue dans l'ordre à partir de la série DepartMode (JoueOrdre();).

XII - La fonction Correct

bool __fastcall TWave::Correct()

{

int i;

for(i=0;i<Serie->Count;i++)

if(DepartMode->Text==IntToStr(i+1)) return true;

return(DepartMode->Text=="A");

}

On cherche à savoir si ce qui est entré dans DepartMode est correct ou non. Pour que ce soit correct, il faut que ce TLabeledEdit contienne un nombre entier compris entre 1 et Serie->Count sinon il devra contenir la lettre A pour invoquer le mode aléatoire. Donc pour tous les indices de la série de base (for(i=0;i<Serie->Count;i++)), si DepartMode est égal à l'indice incrémenté (if(DepartMode->Text==IntToStr(i+1))), le retour se fait avec true (return true;), car la saisie est correcte. On teste ici l'indice incrémenté i+1, car les indices de la TStringList vont de 0 à Serie->Count-1 alors que ce qui est censé être saisi dans DepartMode doit se situer entre 1 et Série->Count. Si le retour n'a jamais lieu, DepartMode ne contient pas un entier compris entre 1 et Serie->Count, il ne reste alors que la lettre A comme possibilité, c'est cet ultime test qui donnera la réponse dans ce cas (return(DepartMode->Text=="A");).

XIII - La procédure JoueOrdre

void __fastcall TWave::JoueOrdre()

{

int i=StrToInt(DepartMode->Text);

int n=PosIntegral(Serie->Strings[i-1]);

while(true)

{

if(!JoueRep(Integral->Strings[n++])) return;

if(n==Integral->Count) n=0;

}

}

On exécute cette procédure parce que DepartMode contient un nombre correct compris entre 1 et Serie->Count. Il s'agit donc de jouer les fichiers Wave dans leur ordre d'apparition à partir de la série DepartMode. On met dans l'entier i le numéro de série choisi (int i=StrToInt(DepartMode->Text);) puis on calcule dans l'entier n la première occurrence dans la TStringList Integral correspondant à cette série d'indice i-1 soit Serie->Strings[i-1] (int n=PosIntegral(Serie->Strings[i-1]);). Cela signifie que Integral->Strings[n] représentera le premier répertoire à visiter. Ensuite, on entre dans une boucle infinie (while(true)). Si une interruption a été demandée par l'utilisateur au moment de jouer tous les fichiers du répertoire pointé (if(!JoueRep(Integral->Strings[n++]))), on arrête tout (return;). Sinon, le répertoire entier a été joué, on cherche à savoir si n, après avoir été incrémenté, est égal au nombre de répertoires valides (if(n==Integral->Count)), si c'est le cas, on remet n à 0 (n=0;) pour recommencer la lecture des répertoires à partir du premier de la liste.

XIV - La fonction PosIntegral

int __fastcall TWave::PosIntegral(AnsiString Rep)

{

AnsiString Nom;

int i=0;

while(true)

{

Nom=Integral->Strings[i++];

if(Nom.AnsiPos(Rep)==1) return(--i);

}

}

La fonction reçoit une série de base en argument, il s'agit de trouver l'indice de l'occurrence où ce répertoire apparaît pour la première fois dans Integral. On initialise l'indice à tester à 0 (int i=0;). On entre dans une boucle infinie (while(true)), à chaque occurrence on lit le nom du répertoire (Nom=Integral->Strings[i++];). Si le répertoire lu dans l'AnsiString Nom commence par la série envoyée (if(Nom.AnsiPos(Rep)==1)), on retourne à l'appelant avec le bon indice (return(--i);) c'est-à-dire i décrémenté, car i a été précédemment incrémenté pour le cas où il aurait fallu tester l'occurrence suivante. Il n'y a rien d'autre, comme toutes séries sont valides, il est absolument certain que Rep sera trouvé dans une des Integral->Count occurrences de la TStringList.

XV - La fonction JoueRep

bool __fastcall TWave::JoueRep(AnsiString R)

{

ListeSons->Clear();

Ajoute(R);

return JoueTout();

}

Cette fonction liste la totalité des fichiers Wave du répertoire R envoyé en argument et joue tous les fichiers. La fonction renvoie un booléen qui dit si l'utilisateur a demandé l'arrêt ou non des lectures. On remet à zéro la TStringList qui contiendra tous les sons à jouer (ListeSons->Clear();). On ajoute à cette liste la totalité des fichiers Wave trouvés dans R (Ajoute(R);). Puis on joue toute cette liste en renvoyant une information booléenne (return JoueTout();). Cette information binaire dit s'il faut continuer à jouer les fichiers ou non.

XVI - La procédure Ajoute

void __fastcall TWave::Ajoute(AnsiString R)

{

TSearchRec SRec;

FindFirst(R+"\\*.wav", faAnyFile, SRec);

do ListeSons->Add(R + "\\" + SRec.Name); while(!FindNext(SRec));

FindClose(SRec);

}

Cette procédure ajoute à la TStringList ListeSons tous les sons trouvés dans le répertoire R envoyé en argument. On sait qu'il y en aura au moins un, on positionne la variable SRec en fonction de la première occurrence (FindFirst(R+"\\*.wav", faAnyFile, SRec);). On ne teste pas ici le résultat booléen de la fonction étant donné qu'on est certain que R contient au moins un Wave. On entre alors dans une boucle en do, à chaque occurrence, celle-ci est ajoutée à la liste (ListeSons->Add(R + "\\" + SRec.Name);) et ce, tant qu'il y a des fichiers Wave à mémoriser (while(!FindNext(SRec));). On restitue pour finir la mémoire utilisée (FindClose(SRec);). Donc au retour, ListeSons contiendra la totalité des fichiers Wave, chemin et nom, prêts à être joués en boucle.

XVII - La fonction JoueTout

bool __fastcall TWave::JoueTout()

{

int i;

POINT P;

GetCursorPos(&P);

for(i=0;i<ListeSons->Count;i++)

if(abs(JoueUnite(i)-P.x)>MaxDecal) return false;

return true;

}

La fonction JoueTout liste la totalité des sons et envoie chacun d'eux à JoueUnite. La fonction renvoie un booléen qui indiquera à l'appelant s'il faut arrêter ou non la suite des lectures. Pour l'arrêt, nous utilisons ici une méthode particulière qui consiste à dire que si le curseur s'écarte de plus de MaxDecal pixels (constante initialisée à 100), alors on arrête. En effet, du fait que tout le travail s'effectue sous le coup de l'événement OnClick du bouton Joue, il n'est pas possible de tester par exemple le clic de la souris, car les événements Windows ne nous arrivent pas. On a donc opté pour cette solution qui consiste à mémoriser la position du curseur avant de faire jouer un Wave puis à renvoyer false à l'appelant pour arrêter si un décalage de plus de MaxDecal pixels en x est détecté. Il suffit donc de déplacer suffisamment le curseur à gauche ou à droite pour que le programme s'arrête. On déclare une structure POINT (POINT P;). On lit la position du curseur dans P (GetCursorPos(&P);). Le curseur se trouve donc en P.x (x minuscule). Ensuite, pour tous les indices de la liste des sons (for(i=0;i<ListeSons->Count;i++)), si après avoir joué l'unité d'indice i le décalage en x du curseur excède MaxDecal (if(abs(JoueUnite(i)-P.x)>MaxDecal)), le retour se fait avec false (return false;), ce qui provoquera l'arrêt du programme, sinon le retour se fait avec true (return true;) et l'appelant décidera de continuer de jouer les fichiers Wave.

XVIII - La fonction JoueUnite

int __fastcall TWave::JoueUnite(int i)

{

POINT P;

AnsiString A=ListeSons->Strings[i];

FichierWave->Caption=A;

Update();

Media->FileName = A;

Media->Open();

Media->Wait=true;

Media->Play();

Sleep(300);

GetCursorPos(&P);

return(P.x);

}

Cette fonction reçoit l'indice i de la TStringList ListeSons à exécuter et renvoie la position du curseur après avoir exécuté le Wave. On lit dans un AnsiString A le fichier Wave à exécuter (AnsiString A=ListeSons->Strings[i];). On écrit ce nom dans le TLabel FichierWave (FichierWave->Caption=A;). Un update de la forme principale est nécessaire du fait qu'on est toujours sous le coup de l'événement OnClick, l'affichage ne se ferait pas sans cet update (Update();). Ensuite on renseigne la propriété FileName du MediaPlayer (Media->FileName = A;). On ouvre le MediaPlayer (Media->Open();), on met sa propriété Wait à true (Media->Wait=true;), ce qui signifie qu'au moment du Play, le programme ne rend pas la main immédiatement, mais attend que le fichier ait été joué intégralement. On joue le fichier (Media->Play();), mais comme Wait est à true, la main ne sera rendue qu'après exécution. Vous pourrez vérifier ce point en mettant le curseur sur cette instruction puis en faisant F4 (exécuter jusqu'à la position du curseur). Faites ensuite F8 pour exécuter l'instruction, vous verrez que vous n'aurez la main qu'après exécution du Wave. Ensuite, on attend 300 millisecondes (Sleep(300);) pour laisser une fraction de seconde entre les exécutions. On lit la position du curseur (GetCursorPos(&P);) et on renvoie à l'appelant la coordonnée en x (return(P.x);). Comme l'appelant a mémorisé P.x avant l'appel, il n'aura qu'à comparer avec cette nouvelle donnée pour décider de s'arrêter ou non.

XIX - La procédure JoueAlea

void __fastcall TWave::JoueAlea()

{

int i;

AnsiString N;

POINT P;

GetCursorPos(&P);

ListeSons->Clear();

for(i=0;i<Integral->Count;i++) Ajoute(Integral->Strings[i]);

Randomize();

while(true)

if(abs(JoueUnite(RandomRange(0,ListeSons->Count-1))-P.x)>MaxDecal) return;

}

Cette procédure est appelée parce que l'utilisateur a saisi la lettre A dans le TLabeledEdit DepartMode. On mémorise d'abord la position du curseur (GetCursorPos(&P);). Puis on va établir la liste totale des sons possibles. On commence par remettre à zéro la liste des sons (ListeSons->Clear();). Puis pour chaque répertoire inscrit dans Integral (for(i=0;i<Integral->Count;i++)), on ajoute les noms des fichiers Wave qui s'y trouvent (Ajoute(Integral->Strings[i]);). On sait que pour chaque Integral->Strings[i], il existe au moins un Wave. Puis on initialise la série aléatoire (Randomize();). Cette précaution est nécessaire sinon la suite aléatoire serait toujours la même à chaque exécution. On s'assure ainsi qu'à chaque exécution, il y aura un aléatoire nouveau. Puis on entre dans une boucle infinie (while(true)) et à chaque itération, on joue le fichier dont l'indice est choisi par la fonction RandomRange qui a pour arguments les limites du choix bornes incluses à savoir 0 et ListeSons->Count-1. Comme la fonction JoueUnite renvoie la position du curseur après exécution du Wave choisi au hasard, on en profite pour tester si la différence en x est supérieure à MaxDecal (if(abs(JoueUnite(RandomRange(0,ListeSons->Count-1))-P.x)>MaxDecal)), si c'est le cas, on retourne à l'appelant (return;), ce qui a pour effet d'arrêter l'exécution des fichiers Wave. Sinon la boucle infinie continue et on joue au hasard un fichier de la liste ayant pour indice un nombre compris entre 0 et ListeSons->Count-1.

XX - L'événement OnDestroy

void __fastcall TWave::FormDestroy(TObject *Sender)

{

delete ListeSons;

delete Serie;

delete Integral;

}

Nous avions instancié trois TStringList à l'initialisation, on restitue la mémoire avant la destruction de la fenêtre principale et donc la fin de cette petite application.

XXI - Pour faire fonctionner ce tutoriel

1) Créez un répertoire nouveau dans votre disque dur où vous copierez des fichiers Wave de votre choix et dans n'importe quelle architecture d'arborescence à partir de ce point de départ. Si vous avez un CD-ROM de « Talk to me », mettez dans ce répertoire de base les six séries, chacune doit contenir trois sous-répertoires, question, réponse, locution. Vous pouvez renommer ces séries par exemple SERIE1, SERIE2, etc., mais ce n'est pas obligatoire puisque le programme scrute l'arborescence indépendamment des noms de répertoire.

2) Entrez dans C++ Builder 6.

3) Sauvegardez immédiatement le projet vide (Fichier|Enregistrer le projet sous) en choisissant SonWave à la place de unit1 et son à la place de project1. Le mieux est de créer un répertoire nouveau particulier à ce projet avant cette sauvegarde. Vous pouvez toujours le faire au même moment puisque le formulaire de sauvegarde vous permet de créer directement un sous-répertoire sans qu'il soit nécessaire de revenir sous Windows.

4) Donnez à la forme principale le nom Wave (propriété Name dans l'inspecteur d'objets).

5) Double-cliquez dans l'inspecteur d'objets sur l'événement OnDestroy de la fenêtre principale pour faire exister cet événement.

6) Mettez un bouton (palette standard) sur la forme principale et appelez-le Joue (propriété Name dans l'inspecteur d'objets).

7) Double-cliquez sur le bouton pour faire exister l'événement OnClick.

8) Mettez sur la forme principale un TLabelEdit (palette supplément) et appelez-le DepartMode (propriété Name dans l'inspecteur d'objets).

9) Mettez sur la forme principale un Label (palette standard) et appelez-le FichierWave (propriété Name dans l'inspecteur d'objets).

10) Mettez sur la forme principale un MediaPlayer (palette système) et appelez-le Media (propriété Name dans l'inspecteur d'objets). Tous les objets sont maintenant installés et nommés, sans trop vous occuper de la présentation pour le moment, vous obtenez quelque chose de ce genre.

11) Mettez la propriété Visible du MediaPlayer à false dans l'inspecteur d'objets.

12) Sélectionnez l'entête SonWave.h et ajoutez les variables et méthodes dans la zone utilisateur de la classe principale. Vous trouverez cette liste plus haut dans le paragraphe intitulé « La classe principale » peu après début de ce tutoriel, il s'agit des éléments numérotés. Il faut donc copier les lignes (ligne 17 du code RepBase et lignes 25 à 60 code Classe principale) de ce HTML et les coller dans l'entête SonWave.h au bon endroit c'est-à-dire à la fin de la classe à l'emplacement réservé à l'utilisateur. Vous venez donc de rajouter dans la classe principale les déclarations des trois TStringList ainsi que le prototype de toutes les procédures et fonctions du tutoriel.

13) Téléchargez le programme C++, c'est un simple fichier texte.

14) Effacez tout ce qui se trouve dans SonWave.cppet mettez à la place le contenu du fichier texte que vous venez de télécharger. Par exemple, sélectionnez tout dans le fichier téléchargé (contrôle A), copiez ce tout (contrôle C), revenez au source C++ SonWave.cpp, sélectionnez tout (contrôle A) et copiez tout (contrôle V).

15) Renseignez correctement la constante RepBasedans SonWave.cpp. Cette constante se trouve juste après la déclaration du pointeur de la fenêtre principale (TWave *Wave;) en début de listing C++. Dans cette chaîne vous devez inscrire le nom du répertoire de base que vous avez préparé et où se trouvent vos fichiers Wave avec ou sans sous-répertoires.

16) Sauvegardez le projet C++ Builder (disquettes en cascade)

17) Faites F9 pour compiler et exécuter.