M1103 Feuille 2

Le but de cette feuille d’exercices est de générer des fichiers audio à partir d’une description textuelle de chansons.

Téléchargez le fichier code.zip et ajoutez son contenu (les fichiers note.cpp, note.h, song.cpp, song.h, test.cpp, asserts.h) à un nouveau projet Code::Blocks. Placer les fichiers .txt dans le dossier ../songs/ par rapport à votre exécutable.

Vous pouvez tester vos fonctions en lançant le programme dont la fonction main est définie dans le fichier test.cpp.

Exercice 1 — Notes

Une note de musique est décrite par sa hauteur et sa durée que nous expliquerons séparément dans la suite.

Nous utiliserons le système anglo-saxon pour décrire la hauteur d’une note. La description de la hauteur contient un caractère entre A et G, et un numéro d’octave. Une octave commence avec un C et finit avec un B. L’image suivante contient les tons entre le C de l’octave numéro 4 (ou C4) au G de l’octave numéro 5 (ou G5) :

Notes de musique
Notes de musique

Ces tons correspondent aux touches blanches d’un piano :

Touches d’un piano
Touches d’un piano

Nous remarquons que le piano contient des touches noires aussi. Elles représentent des demi-tons qui sont placés entre certaines touches blanches. Plus précisément, il y a des touches noires entre chaque couple de touches blanches consécutives, sauf entre E et F, et entre B et C. Pour décrire un demi-ton, nous prenons un ton adjacent et le modifions avec le caractère # ou le caractère b directement après un des caractères A à G. Le caractère # modifie un ton au demi-ton au-dessus. Le caractère b modifie un ton au demi-ton au-dessous. Par exemple, la chaîne de caractères D#4 décrit le demi-ton entre le D et le E de l’octave numéro 4. La chaîne Eb4 décrit le même demi-ton.

La fréquence d’une note est mesurée en Hertz. La fréquence du ton A4 est égale à 440 Hz. En passant d’un ton au même ton une octave plus haute, on multiplie la fréquence par 2. La fréquence de A5 est donc égale à \(2\times 440\) Hz \(= 880\) Hz. Celle de A3 est égale à \(440/2\) Hz = \(220\) Hz. Plus généralement, parce qu’il y a 12 demi-tons dans une octave, la fréquence d’une note est égale à \[2^{n/12} \times 440 \text{ Hz}\]\(n\) est le nombre de demi-tons de la note à A4 (négatif si plus basse que A4, positif si plus haute).

Pour plus de détails, voir https://fr.wikipedia.org/wiki/Hauteur_(musique).

La durée d’une note est mesurée en fraction d’une mesure (dont la durée est déterminée par le tempo de la chanson). Elle sera représentée par une fraction de la forme a/b. Par exemple, la durée d’une note qui dure un quart d’une mesure sera écrite 1/4.

La représentation complète d’une note est celle de la hauteur suivie de celle de la durée, séparées par le caractère @. Par exemple, une note de hauteur A4 qui dure un quart d’une mesure sera écrite A4@1/4.

a — Conversion caractère vers demi-ton

Nous commençons avec le calcul du numéro de demi-ton à l’intérieur d’une octave relatif au C de l’octave.

Compléter la fonction letterToSemitone qui prend un caractère et qui renvoie un entier. L’entier renvoyé doit être égal au nombre de demi-tons entre C et le caractère donné. Par exemple, le caractère C produit l’entier 0 et le caractère F produit l’entier 5.

Si le caractère ne représente pas de ton, lancer une exception du type invalid_argument.

b — Conversion caractère et altération vers demi-ton

Vous pouvez utiliser votre fonction letterToSemitone. Pour cela, il est utile de savoir que vous pouvez utiliser un string comme un vecteur de caractères. En particulier, vous pouvez accéder à la longueur du string str avec str.size() et à son élément à l’indice i avec str[i].

Nous allons maintenant généraliser la conversion pour prendre en compte les altérations # et b, toujours en restant à l’intérieur d’une octave. Un exemple est F#.

Compléter la fonction letterAndSignToSemitone qui prend un string et qui renvoie un entier. L’entier renvoyé doit être égal au nombre de demi-tons entre C et le demi-ton décrit par le string donné.

Si le string ne représente pas de demi-ton, lancer une exception du type invalid_argument.

c — Conversion hauteur complète vers demi-ton

Pour décomposer le string str en une partie avec le caractère et altération et une deuxième partie avec le numéro d’octave, la fonction membre substr de la classe string sera utile. Vous trouvez sa documentation ici. Par exemple, l’appel str.substr(0,2) renvoie un string contenant les deux premiers caractères du string str.

Pour transformer un string qui représente un entier en valeur de type int, utiliser la fonction stoi. Vous trouvez sa documentation ici.

Nous allons maintenant prendre en compte le numéro d’octave et mesurer les demi-tons non pas relatifs au C de l’octave courante mais relatifs au ton A4. Un exemple est A#3.

Compléter la fonction stringToSemitone qui prend un string et qui renvoie un entier. L’entier renvoyé doit être égal au nombre de demi-tons entre A4 et le demi-ton décrit par le string donné.

Si le string ne représente pas de demi-ton, lancer une exception du type invalid_argument.

d — Fréquences

Dès que nous connaissons la distance en demi-tons à A4, nous pouvons calculer la fréquence d’une note en utilisant la formule donnée ci-dessus. Vous pouvez utiliser l’expression round(440 * exp2((double)semi/12)) pour calculer la fréquence d’une note qui est à semi demi-tons de A4.

Compléter la fonction stringToFrequency qui prend un string et qui renvoie un entier. L’entier renvoyé doit être égal à la fréquence du demi-ton décrit par le string, arrondie au plus proche entier.

Si le string ne représente pas de demi-ton, lancer une exception du type invalid_argument.

e — La classe Note

La classe Note encapsule les données d’une note, c’est-à-dire la fréquence et la durée d’une note. Nous avons déjà traité le calcul d’une fréquence à partir d’un string. Pour compléter une note, il reste à traiter sa durée.

Nous allons donc écrire un constructeur pour la classe Note qui prend deux string, un pour la fréquence et un pour la durée. (Par exemple A4 et 1/4.) Pour pouvoir utiliser ces données à l’extérieur de la classe, nous allons les retourner dans les deux fonctions frequency() et duration().

Compléter le constructeur et les deux fonctions décrits ci-dessus. La fonction frequency() doit retourner la fréquence de la note en Hz arrondie au plus proche entier. La fonction duration() doit retourner la durée de la note en multiples de huitième de mesure.

f — Pauses

Nous représentons des pauses dans une chanson par une note avec la fréquence 0. Pour simplifier, nous fixons la durée d’une pause à 1/8. Une pause sera représentée par un string vide. Nous rappelons qu’une note sera représentée par un string de la forme A4@1/4.

Compléter le constructeur de la classe Note qui prend un string. Si le string représente une note, initialiser les données membres de l’objet avec cette note. Si le string est vide, initialiser la fréquence à 0 et la durée à 1/8.

Exercice 2 — Chansons

Une chanson est définie par une suite de notes. Nous allons les stocker dans un vecteur de notes. Ce vecteur sera encapsulé dans la classe Song. À partir de cette suite de notes, nous allons générer une forme d’onde pour générer un fichier audio de format .wav. Le but est de transformer un fichier .txt contenant une suite de descriptions de notes en fichier .wav que nous pouvons écouter.

Voici un exemple d’une description textuelle d’une partie d’une chanson :

G4@1/8
A4@1/8
G4@1/8
F4@1/8
E4@1/8
F4@1/8
G4@1/8

D4@1/8
E4@1/8
F4@1/8

a — Rajout de notes

Nous commençons avec le rajout de notes à une chanson. Pour cela, nous allons implémenter une fonction membre addNote de la classe Song pour rajouter une note à la fois et mettre à jour la durée totale de la chanson. La fonction membre duration retournera cette durée totale pour pouvoir l’utiliser à l’extérieur de la classe.

Compléter les fonctions addNote et duration décrites ci-dessus.

b — Forme d’onde

Pour générer la forme d’onde de notre chanson, nous parcourons dans la fonction membre waveform() le vecteur des notes et concaténons les formes d’ondes des notes individuelles qui sont calculées par la fonction noteToWaveform.

Compléter la fonction membre waveform décrite ci-dessus.

c — Génération de fichiers audio

En théorie, vous avez maintenant déjà programmé tout ce qu’il faut pour générer des fichiers audio à partir des fichiers .txt que nous vous avons fournis. Si vous avez placé les fichiers .txt dans le bon dossier, votre programme va générer des fichiers .wav que vous pouvez écouter pour vérifier votre solution.

retour au site web du cours

This work is a derivative of “Music” by David J. Malan of Harvard University, used under CC BY NC SA. This work is licensed under CC BY NC SA.