Une introduction aux sons en Pharo

GNU/Linux Magazine n° 178 | janvier 2015 | Xavier Messner - Stéphane Ducasse
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
Dans cet article, nous allons vous présenter une facette de Pharo qui n'est pas habituellement mise en avant. Ce domaine, c'est la synthèse sonore. Si nous avons besoin de sonoriser un peu notre tout nouveau programme ce qui nous vient à l'esprit c'est l'utilisation du mp3 pour jouer la musique en fond sonore ou des sons digitalisés pour les bruitages. Dans Pharo nous disposons d'outils et bibliothèques permettant de créer de toute pièce du son synthétisé pour nos productions. Nous allons vous montrer comment faire cela en quelques lignes de code seulement.

Tout d'abord il faut récupérer et installer la dernière version 3.0 de Pharo à l'adresse: http://www.pharo.org/. Le plus simple restant d'exécuter le script de téléchargement qui suit dans un shell.

curl get.pharo.org/30+vmLatest | bash

Si curl n'est pas disponible:

wget -O- get.pharo.org/30+vmLatest | bash

D'autres scripts de téléchargement sont disponibles à l'adresse http://get.pharo.org. L'interface graphique se lance avec la commande ./pharo-ui Pharo.image.

Vous devez ensuite charger le paquet PharoSound en utilisant :

- soit le Configuration Browser (comme le montre la figure 1) qui se trouve dans le menu Tools (entrer « Sound » dans la zone de texte pour sélectionner le paquet et presser sur le bouton Install Stable Version).

Fig. 1 : Configuration Browser

- soit Gofer, le gestionnaire de paquets:

Gofer new

 smalltalkhubUser: 'PharoExtras' project: 'Sound';

 package: 'ConfigurationOfPharoSound';

 load.

(#ConfigurationOfPharoSound asClass project version: #stable) load

Mais, au fait, c'est quoi Pharo ?

Pharo est un langage à objets, pur et dynamiquement typé. Il est distribué sous licence MIT et tourne sur iOS, OSX, Linux, Windows, Android, et Raspberry. Il est inspiré de Smalltalk et propose une alternative solide aux implémentations de Smalltalk propriétaires. 

La syntaxe de Pharo tient sur une carte postale et son modèle objet est très simple, le tout alliant simplicité et élégance: 

- Les booléens, nombres, chaînes, exceptions, collections, compilateurs, souris, écrans sont tous des objets instances de classes. 

- Les objets reçoivent des messages et exécutent les méthodes correspondantes. Les classes sont organisées en héritage simple.

- Les méthodes ont une visibilité publique et les attributs une visibilité protégée.

Pharo offre une pile Web très puissante avec des frameworks pour construire des applications Web comme Seaside (http://www.seaside.st), Magritte (un framework de meta-description) pour éviter la création fastidieuse de formulaires, et des bibliothèques pour le stockage soit sous forme d’objets sérialisés (Fuel, STON), soit en base de données relationnelles avec Postgres ou noSQL (comme Voyage une surcouche à MongoDB).

Des serializeurs de données comme NeoJSON, NeoCSV, et STON, permettent une interaction et une manipulation facile des données.

La couche bas niveau de communication est basée sur Zinc un serveur HTTP/S éprouvé et avec une implémentation de qualité.

Enfin, Pharo ne se limite pas au développement Web : des frameworks permettent la définition de visualisation (http://www.objectprofile.com), d’outils d’analyse de code (http://www.moosetechnologies), ou encore de canevas vecteurs graphiques. Un projet au Chili consiste même à utiliser Pharo comme base pour un moteur de jeu 3D (https://www.youtube.com/watch?v=1Nze9tnwYxY).

1. Vérifications

Avant de nous familiariser avec les différentes méthodes proposées, nous allons nous assurer que notre environnement puisse jouer un son convenablement. Pour cela il suffit d'ouvrir un workspace (depuis le menu Tools) et d'évaluer la ligne de code suivante en sélectionnant l'expression puis en utilisant l'item Do it du menu :

FMSound organ1 play.

Nous vous expliquerons par la suite ce dont il s'agit. Notez que dans un workspace pour exécuter une ligne il suffit d'y positionner le curseur de la souris et de presser<alt> + <d> (cela équivaut à sélectionner la ligne et utiliser le menu). Pour sélectionner un bloc d'instructions rapidement, placez la souris au tout début ou à la fin du bloc et cliquez, cela a pour effet de tout sélectionner.

Si vous obtenez du son, vous pouvez passer au paragraphe suivant, sinon voici quelques pistes pour obtenir du son :

- Activation du son dans Pharo : vérifiez que le son est activé dans votre image Pharo. Pour cela, ouvrez le Settings Browser depuis le menu Tools. Tapez ensuite « sound » dans la barre et cliquez sur les boutons radio pour obtenir une configuration proche de celle présentée en figure 2.

Fig. 2 : Boîte de dialogue du paramétrage du son

- Le volume sonore de votre système est trop bas ou en mode muet.

- Pulseaudio est activé (sur pratiquement toutes les distributions actuelles). Par défaut l'archive Linux n'embarque pas de plugin pulseaudio, seule l'utilisation du plugin Alsa est possible. Nous allons donc désactiver le serveur sonore de manière temporaire. Il ne doit pas être relancé automatiquement lorsque nous l'arrêterons :

- Créer le fichier /home/[user]/.pulse/client.conf s'il n'existe pas ;

- Ajouter le paramètre autospawn=no;

- Supprimer les processus en cours avec la commande pulseaudio -k avec votre compte utilisateur. Pour le relancer dès que vous le souhaitez, lancez la commande pulseaudio -D.

Nous pouvons maintenant relancer Pharo.

Il peut également arriver que le processus sonore ne fonctionne plus. Vous pouvez utiliser les commandes suivantes pour le réinitialiser :

SoundPlayer stopPlayerProcess.

SoundPlayer startUp

Une petite astuce avant d'aller plus loin concernant le rendu sonore : par défaut, de la réverbération est ajoutée au son joué. Dans certaines conditions, cela peut amener une certaine douceur dans le rendu, mais dans notre cas de découverte cela peut aussi fausser ce que nous entendons. Pour désactiver le rendu avec réverbération, lancer la commande :

SoundPlayer stopReverb

Ou, si vous préférez la relancer, vous pouvez le faire avec la commande:

SoundPlayer startReverb

2. Les Bases de la synthèse FM par l'exemple

Maintenant que nous nous sommes assurés que le son était fonctionnel, nous allons découvrir comment celui-ci est généré. Prenons le code de la méthode qui définit l'instrument organ1 :

FMSound class>>organ1

"FMSound organ1 play"

"(FMSound majorScaleOn: FMSound organ1) play"

 

| sound |

sound := self new.

sound addEnvelope: (VolumeEnvelope

points: { 0@0 . 60@1.0 . 110@0.8 . 200@1.0 . 250@0.0}

loopStart: 2 loopEnd: 4).

sound setPitch: 440.0 duration: 1.0 loudness: 0.5.

^ snd

Étudions cette méthode. Tout d'abord, elle crée une nouvelle instance de la classe FMSound avec sound := self new. Elle définit ensuite un ensemble de points afin de créer une enveloppe de volume. Pour chaque point la première donnée exprime une durée en milliseconde, la seconde (après le @) un volume sous forme d'un réel compris entre 0 et 1 (1 étant égal à 100% du volume).

sound addEnvelope: (VolumeEnvelope

points: { 0@0 . 60@1.0 . 110@0.8 . 200@1.0 . 250@0.0}

loopStart: 2 loopEnd: 4).

Nous remarquons qu'une enveloppe se caractérise d'après un ensemble de points et d'une boucle. La méthode associée est points:loopStart:loopEnd:. Une boucle peut aussi bien utiliser l'ensemble des points définis ou 1 seul.

Par exemple ici on ajoute au son une enveloppe avec une boucle allant des indices 2 à 4 (l'indice 1 correspondant à la première valeur du tableau). La séquence suivante est jouée pour des valeurs oscillant entre 0.8 et 1. Le volume du son semble vibrer :

60@1.0 . 110@0.8 . 200@1.0

Si nous avions voulu maintenir le volume à un certain niveau, nous aurions écrit l'enveloppe comme ceci :

sound addEnvelope: (VolumeEnvelope

points: { 0@0 . 60@1.0 . 110@0.8 . 200@1.0 . 250@0.0}

loopStart: 5 loopEnd: 5).

Le son se termine avec la valeur 250@0.0 et une boucle maintenant le volume à 0 pendant toute sa durée.

Finalement, la méthode retourne le son avec comme paramètre la note exprimée en Hz (voir le tableau des fréquences en figure 3), la durée en seconde et un volume global de la note. Ce volume sera ensuite modulé grâce à l'enveloppe qui est définie.

^ sound setPitch: 440.0 duration: 1.0 loudness: 0.5

Fig. 3 : Tableau de correspondance entre les notes anglaises et la fréquence en hertz

3. Comment faire pour jouer notre propre son ?

Nous allons vous montrer comment nous allons créer notre propre son. Commençons par jouer un LA de la manière suivante:

(FMSound new setPitch: 440 duration: 1.0 loudness: 1) play

Le son est joué à une fréquence de 440 Hz pendant 1 seconde avec un volume maximum.

La classe FMSound permet la génération de sons sinusoïdaux. Ça donne un son assez doux et régulier. La figure 4 montre à quoi ressemble la courbe.

Fig. 4 : Courbe sinusoïdale classique

Une chose à savoir est, qu'au lieu de définir une note par sa fréquence nous pouvons l'écrire dans sa notation anglaise. Le tableau de la figure 5 va nous indiquer les correspondances pour plus de facilité. Par exemple, le LA est représenté dans la notation anglaise par un « a ». Pour obtenir le LA de la quatrième octave nous demandons a4.

Le LA de tout à l'heure peut s'écrire maintenant sous cette forme :

(FMSound new setPitch: 'a4' duration: 1.0 loudness: 1) play

Fig. 5 : Tableau de correspondance entre les notes anglaises et les notes françaises

Le SOL# par exemple s'écrit :

(FMSound new setPitch: 'g#4' duration: 1.0 loudness: 1) play

3. Jouer plusieurs sons en séquence

Jouer un son c'est bien, mais en jouer deux c'est mieux. Pour cela nous avons à notre disposition une classe nommée SequentialSound. Comme son nom l'indique, nous allons pouvoir jouer les sons de manière séquentielle :

| seq |

seq := SequentialSound new.

seq add: (FMSound new setPitch: 'g#4' duration: 0.5 loudness: 1).

seq add: (FMSound new setPitch: 'a3' duration: 0.5 loudness: 1).

seq add: (FMSound new setPitch: 'g#4' duration:0.5 loudness: 1).

seq play.

Si nous écoutons les sons générés, nous pouvons percevoir un petit clap entre chaque transition de notes. Afin d'éviter cela, nous allons appliquer une enveloppe de volume sur chacune des notes. Ceci est fait comme suit :

points := { 0@0 . 10@1 . 400@0.4 . 500@0}.

La note jouée est à son maximum au début puis repasse progressivement à 0 au bout de 500ms. L'enveloppe étant définie sur une durée égale à la durée de la note, la transition se fait plus naturellement et sonne mieux à l'oreille.

Voyons ce que donne notre code précédent avec une enveloppe de volume :

| seq snd1 snd2 snd3 points |

"Declaration des sons"

seq := SequentialSound new.

snd1 := FMSound new.

snd2 := FMSound new.

snd3 := FMSound new.

 

"Creation du volume pour les instruments"

points := { 0@0 . 10@1 . 400@0.4 . 500@0}.

 

"Generation des notes"

snd1 setPitch: 'g#4' duration:0.5 loudness: 1.

snd2 setPitch: 'a3' duration:0.5 loudness: 1.

snd3 setPitch: 'g#4' duration:0.5 loudness: 1.

 

"Application de l'enveloppe aux sons"

snd1 addEnvelope: (VolumeEnvelope points: points loopStart: 4 loopEnd: 4).

snd2 addEnvelope: (VolumeEnvelope points: points loopStart: 4 loopEnd: 4).

snd3 addEnvelope: (VolumeEnvelope points: points loopStart: 4 loopEnd: 4).

 

"Les sons les uns a la suite des autres"

seq add: snd1.

seq add: snd2.

seq add: snd3.

 

"On joue le tout"

seq play.

La séquence est maintenant bien plus agréable à écouter !

4. Jouer plusieurs sons simultanément

Jouer des sons en séquence c'est bien, mais pas encore suffisant pour donner une âme à notre production. Pour cela nous allons utiliser la classe MixedSound. Elle va littéralement mixer plusieurs sons en même temps :

| mix snd1 snd2 points |

mix := MixedSound new.

snd1 := FMSound new.

snd2 := FMSound new.

 

"Creation du volume pour les instruments"

points := { 0@0 . 10@1 . 400@0.4 . 500@0}.

 

"Generation des notes"

snd1 setPitch: 'a4' duration:1 loudness: 1.

snd2 setPitch: 'a3' duration:1 loudness: 1.

 

"Application de l'enveloppe aux sons"

snd1 addEnvelope: (VolumeEnvelope points: points loopStart: 4 loopEnd: 4).

snd2 addEnvelope: (VolumeEnvelope points: points loopStart: 4 loopEnd: 4).

 

mix add: snd1 pan: 0; add: snd2 pan: 1.

 

mix play.

Modifions un peu les valeurs de l'attaque à l'aide de la méthode setPitch:duration:loudness: pour obtenir des notes différentes et nous familiariser avec les méthodes. Les enveloppes de volume modifient la courbe du son au cours du temps et peuvent être utilisées pour obtenir des effets particuliers.

Par exemple si nous voulions rajouter un effet d'écho à notre son, il faudrait modifier les points de l'enveloppe de volume par :

points := { 0@1 . 100@0.6 . 200@0 . 300@0.2 . 400@0.1 . 500@0 }.

La déclaration des enveloppes devrait être modifiée par :

snd1 addEnvelope: (VolumeEnvelope points: points loopStart: 6 loopEnd: 6).

snd2 addEnvelope: (VolumeEnvelope points: points loopStart: 6 loopEnd: 6).

Dans notre exemple, nous avons également utilisé le message pan: qui va positionner le son en stéréo. Une valeur de 0 pour gauche et de 1 pour droite (une valeur de 0.5 place le son au milieu).

Nous allons réutiliser l'exemple plus haut et lui rajouter de la stéréo :

| mix seq1 seq2 seq3 snd1 snd2 snd3 points rest rest2 |

"Declaration des sons"

mix := MixedSound new.

rest := RestSound new.

rest2 := RestSound new.

seq1 := SequentialSound new.

seq2 := SequentialSound new.

seq3 := SequentialSound new.

snd1 := FMSound new.

snd2 := FMSound new.

snd3 := FMSound new.

 

"Creation du volume pour les instruments"

points := { 0@1 . 400@0.4 . 500@0}.

 

"Generation des notes"

snd1 setPitch: 'g#4' duration:0.5 loudness: 1.

snd2 setPitch: 'a3' duration:0.5 loudness: 1.

snd3 setPitch: 'g#4' duration:0.5 loudness: 1.

rest duration: 0.5.

rest2 duration: 0.5.

 

"Application de l'enveloppe aux sons"

snd1 addEnvelope: (VolumeEnvelope points: points loopStart: 3 loopEnd: 3).

snd2 addEnvelope: (VolumeEnvelope points: points loopStart: 3 loopEnd: 3).

snd3 addEnvelope: (VolumeEnvelope points: points loopStart: 3 loopEnd: 3).

 

"Les sons les uns a la suite des autres"

seq1 add: snd1.

seq2 add: rest; add: snd2.

seq3 add: rest; add: rest2; add: snd3.

 

mix add: seq1 pan:0.

mix add: seq2 pan:0.5.

mix add: seq3 pan:1.

 

"On joue le tout"

mix play.

Dans ce nouvel exemple, nous avons introduit une nouvelle classe de son RestSound pour créer un silence. Elle ne possède qu'un seul message duration: exprimé en secondes et générant un son plus ou moins long.

La méthode SequentialSound possède toutefois une limitation. Regardons l'exemple suivant pour voir ce qu'il se passe :

| seq snd1 snd2 snd3 |

"Declaration des sons"

seq := SequentialSound new.

snd1 := FMSound new.

snd2 := FMSound new.

snd3 := FMSound new.

 

"Generation des notes"

snd1 setPitch: 'g#4' duration:0.5 loudness: 1.

snd2 setPitch: 'a3' duration:0.5 loudness: 1.

snd3 setPitch: 'g#4' duration:0.5 loudness: 1.

 

"Les sons les uns a la suite des autres"

"Le resultat ne va pas etre celui attendu"

seq add: snd1.

seq add: snd2.

seq add: snd3.

seq add: snd2.

 

"On joue le tout"

seq play.

Normalement nous devrions entendre quatre sons joués à la suite, or il n'y en a que trois. Visiblement nous ne pouvons pas réutiliser un son déjà déclaré. Cette méthode n'est pas adaptée pour ce que nous souhaitons faire. Pour cela il existe une autre classe qui va répondre à notre besoin: QueueSound.

Remplaçons la ligne seq := SequentialSound par seq := QueueSound et écoutons de nouveau notre exemple. Nous entendons bien nos quatre sons. Le tour est joué !

5. Les enveloppes

Nous avons introduit une première enveloppe qui agit sur le volume d'un son. Il y en a une autre qui change la note ou plutôt sa fréquence pendant qu'elle est jouée ; c'est ce qu'on appelle l'attaque de la note, le pitch en anglais.

De la même manière que pour le volume, nous allons définir un ensemble de points qui indiqueront le moment du changement de la fréquence du son. Plus la valeur est haute, plus la note sera haute. Si on indique un chiffre négatif, la note descendra en dessous de la note de départ. Nous définissons donc une enveloppe, mais cette fois-ci une enveloppe d'attaque en utilisant la classe PitchEnvelope :

| snd p |

p := { 0@0 . 100@0.1 . 200@0.2 . 300@(-0.5) . 400@0.7 . 800@0.8 . 1000@1 }.

snd := FMSound new.

snd addEnvelope: (PitchEnvelope points: p loopStart: 7 loopEnd:7).

(snd setPitch: 'a4' duration: 2 loudness: 0.5) play.

La transition entre les différentes fréquences se fait en douceur et sans cassures. Nous allons rajouter à cela une enveloppe de volume pour produire un son plus intéressant :

| snd p p2 |

p := { 0@0 . 100@0.1 . 200@0.2 . 300@(-0.5) . 400@0.7 . 800@0.8 . 1000@1 }.

p2 := { 0@0 . 100@0.1 . 200@0.2 . 300@(-0.5) . 400@0.7 . 800@0.8 . 1000@0 }.

snd := FMSound new.

snd addEnvelope: (PitchEnvelope points: p loopStart: 7 loopEnd: 7).

snd addEnvelope: (VolumeEnvelope points: p2 loopStart: 7 loopEnd: 7).

(snd setPitch: 'a4' duration: 2 loudness: 0.5) play.

Le volume est dans notre exemple très variable, l'ensemble des points a été défini manuellement. Imaginons maintenant que nous voulions le réduire progressivement jusqu'à 0. Nous aurions à définir un certain nombre de points afin de produire une courbe décroissante. C'est faisable, fastidieux et surtout la courbe ne produirait pas l'effet souhaité.

Au lieu de la définir point par point, nous allons utiliser une méthode appelée exponentialDecay: de la classe VolumeEnvelope pour le volume ou la classe PitchEnvelope pour le pitch. La courbe sera de type exponentielle partant du volume le plus élevé pour arriver en douceur à 0. La définition du volume devient alors ceci :

snd addEnvelope: (VolumeEnvelope exponentialDecay: 0.9).

La valeur doit être impérativement plus grande que 0 et plus petite que 1. Pour connaître l'ensemble des points générés il suffit de faire un clic droit sur la ligne de code suivante et de sélectionner Print it dans le menu contextuel :

(VolumeEnvelope exponentialDecay: 0.2) points.

Nous obtenons le résultat suivant

{(0@0.0). (10@1.0). (20@0.2). (30@0.04000000000000001). (50@0.0)}

Plus la valeur est grande, plus le nombre de valeurs sera important.

Nous pouvons visualiser à quoi ressemble notre enveloppe en ouvrant le morph EnvelopeEditorMorph comme ceci :

EnvelopeEditorMorph new openInWorld 

Sélectionnez l'ensemble de la ligne et faites un clic droit puis Do it. Une fenêtre apparaît comme celle présentée en figure 6. Comme le montre la figure 7, vous pouvez l'agrandir en la sélectionnant avec <shift> + clic droit puis en déplaçant l'ancre en bas à droite de la fenêtre.

Fig. 6 : Morph permettant l'édition du son

Fig. 7 : Ancre du morph

Le morph expose les courbes de l'instrument et nous permet de les modifier en déplaçant les points le composant. Vous pouvez passer d'une courbe à l'autre soit en la sélectionnant directement dans la fenêtre, soit en la choisissant dans le menu editing en bas à gauche du morph.

En abscisse, nous avons le temps en millisecondes. L'ordonnée se modifie en fonction de la courbe choisie. Nous avons également trois délimitations représentées par les axes verticaux de couleur orange. Les deux plus à gauche sont utilisées pour la boucle lorsque la note est tenue, ce sont les paramètres loopStart:loopEnd:. La plus à droite, c'est lorsque la note est relâchée. Nous pouvons également les déplacer en fonction de nos besoins en utilisant les triangles orange au-dessus de la courbe des abscisses. À noter qu'il n'est pas possible d'avoir sur le morph une boucle d'une durée inférieure à 50ms, comme il n'est pas possible d'avoir une valeur de début de boucle différente de la valeur de fin. Ce qui paraît logique si nous souhaitons obtenir une boucle harmonieuse.

Pour rajouter un point sur une partie de la courbe, il suffit de cliquer sur les petits triangles verts pour faire apparaître le point correspondant. Ce point peut être ensuite déplacé pour être positionné à la valeur souhaitée.

Je vous invite à tester toutes ces possibilités et à écouter le résultat obtenu grâce au clavier de piano disponible.

Les enveloppes viennent compléter nos connaissances sur la génération du son. Nous savons donc jouer un son, plusieurs sons les uns après les autres ou encore plusieurs joués en même temps. Nous avons entre nos mains pratiquement tous les outils pour écrire notre première mélodie... que disons-nous, notre première super-production !

6. Jouer une mélodie

À quoi peut bien ressembler de la musique produite dans Pharo ? Évaluons la ligne de code suivante dans un workspace et écoutons :

FMSound bachFugue play

La musique est constituée d'une séquence de notes. Nous savons maintenant comment écrire une telle séquence, mais pour une mélodie entière ce n'est pas adapté. Il est nécessaire de faire autrement.

Nous allons décrire l'ensemble des notes dans un tableau. Le format est le suivant :

(note duree volume)

La note peut être écrite sous sa forme littérale ou d'après sa fréquence en hertz. La durée s'exprime en millisecondes. Le volume, quant à lui, est une valeur entre 0 et 1000. Au-delà de 1000 le son peut être amené à saturer, mais cela peut se révéler utile dans certains cas pour donner un effet.

Voici un exemple de déclaration d'une séquence :

notes := #((a3 200 150) (a4 100 150) (g3 150 150))

Nous allons écrire notre premier morceau avec un air qui devrait rappeler de bons souvenirs à tous ceux qui aiment le début de l'ère des jeux vidéos. Nous allons définir la mélodie d'introduction du jeu pacman.

7. Définissons un nouvel élément

Commençons par définir un nouvel instrument. Afin de ne pas compliquer notre exercice, nous allons utiliser la classe FMSound dans laquelle nous allons définir quelques nouvelles méthodes. Notez que pour être plus propres et modulaires, nous devrions définir nos propres paquets et classes.

Pour définir une méthode, nous allons utiliser un navigateur de classe. Pour cela :

- ouvrez le SystemBrowser depuis le menu Tools ;

- dans le panneau de gauche, faites un clic droit puis Find Class ;

- entrez dans la zone de saisie « FMSound » et sélectionnez-la.

Vous devez obtenir un navigateur comme celui de la figure 8.

Fig. 8 : Fenêtre de navigation et d'édition des méthodes

Comme les méthodes que nous définissons sont des méthodes qui créent des instances de FMSound par opposition à des méthodes qui agissent sur des instances de la classe FMSound, nous allons les définir sur le côté classe de la classe FMSound. Pour cela :

- cliquez sur le bouton radio Class side ;

- à l'aide du menu de la troisième liste, en partant de la gauche (liste des protocoles de méthodes - il s'agit de simples classeurs de rangement), ajoutez un protocole (Add protocol) nommé par exemple « new instruments ».

- sélectionnez le nouveau protocole puis ajoutez la méthode fm1 suivante. Pour ajouter une méthode, tapez le corps de la méthode dans le panneau prévu à cet effet (en bas à gauche) puis utilisez l'entrée Accept du menu (clic droit) qui compile la méthode ou alors utilisez le raccourci<alt> + <s>.

fm1

| snd |

snd := self new.

snd addEnvelope: (VolumeEnvelope points: { 0@1 . 300@0 }

loopStart: 2

loopEnd: 2).

snd modulation: 1 ratio: 2.

^ snd

Vous devez obtenir une situation similaire à celle présentée en figure 9.

Fig. 9 : Exemple d'édition de la méthode fm1

Vérifiez que votre nouvel instrument fonctionne en exécutant FMSound fm1 play.

Nous allons définir un autre instrument en copiant celui-ci afin de vous permettre d'expérimenter.

fm2

| snd |

snd := self new.

snd addEnvelope: (VolumeEnvelope points: { 0@1 . 300@0 }

loopStart: 2

loopEnd: 2).

snd modulation: 1 ratio: 2.

^ snd

Déclarons maintenant de la même manière la méthode pacman permettant de mixer l'ensemble des pistes qui vont être créées à l'aide des méthodes pacmanV10: et pacmanV20:.

pacman

^ MixedSound new

add: (self pacmanV1On: self fm1) pan: 0.5;

add: (self pacmanV2On: self fm2) pan: 0.5.

Lorsque vous allez compiler la méthode, Pharo va vérifier que l'ensemble des méthodes appelées existent. Si elles n'existent pas, elles apparaissent en rouge dans le code. Une boîte de dialogue va vous demander de confirmer votre saisie. Il vous suffit de sélectionner le premier choix de la liste. Répétez cette opération autant de fois qu'il y aura de méthodes inconnues.

Les deux méthodes suivantes contiennent l'ensemble des notes à jouer avec le triplet note, durée et volume :

pacmanV1On: aSound

| notes |

notes := #(

(b4 0.144 150) (b5 0.144 150) ('f#5' 0.144 150) ('d#5' 0.144 150)

(b5 0.072 150) ('f#5' 0.144 150) ('d#5' 0.288 150) (c5 0.144 150)

(c6 0.144 150) (g5 0.144 150) (e5 0.144 150) (c6 0.072 150)

(g5 0.216 150) (e5 0.288 150) (b4 0.144 150) (b5 0.144 150)

('f#5' 0.144 150) ('d#5' 0.144 150) (b5 0.072 150) ('f#5' 0.216 150)

('d#5' 0.288 150) ('d#5' 0.072 150) (e5 0.072 150) (f5 0.144 150)

(f5 0.072 150) ('f#5' 0.072 150) (g5 0.144 150) (g5 0.072 150)

('g#5' 0.072 150) (a5 0.144 150) (b5 0.216 150)).

 

^self noteSequenceOn: aSound from: notes.

La méthode noteSequenceOn:from: va générer une séquence de notes (par exemple SequentialSound) à partir d'un tableau de notes pour un instrument donné. Elle va utiliser comme vu précédemment le triplet note, durée, volume.

pacmanV2On: aSound

| notes |

notes := #(

(b1 0.432 500) (b2 0.144 500) (b1 0.432 500) (b2 0.144 500) (c2 0.432 500)

(c3 0.144 500) (c2 0.432 500) (c3 0.144 500) (b1 0.432 500) (b2 0.144 500)

(b1 0.432 500) (b2 0.144 500) ('f#2' 0.288 500) ('g#2' 0.288 500)

('a#2' 0.288 500) (b2 0.288 500)).

 

^self noteSequenceOn: aSound from: notes.

Si votre mélodie utilise des notes à fréquence basse, le volume de celle-ci sera difficilement audible. Vous pouvez alors, pour y remédier, augmenter le volume des notes. Ici nous sommes passés à un volume de 500, contrairement à la précédente mélodie où nous étions restés à 150.

Voilà, il ne reste plus qu'à exécuter la mélodie: FMSound pacman play.

Si vous souhaitez modifier les mélodies, n'oubliez pas de décompiler à chaque fois comme vu précédemment.

8. Jouer un sample

La sonorisation d'une production ne passe pas essentiellement par de la synthèse sonore, parfois il est nécessaire de faire appel à des sons digitalisés reproduisant des bruitages, des voix ou des musiques entières. Nous allons vous montrer comment jouer un sample à partir d'un fichier ou directement depuis la mémoire.

Tout d'abord nous allons télécharger les sons pour notre exemple. Pour cela, exécutez dans une console la commande suivante ou alors téléchargez directement l'archive sur votre ordinateur :

wget https://github.com/xmessner/PharoSoundTutorial/archive/master.zip

Décompressez-la ensuite dans le répertoire de votre choix. Le sous-répertoire sounds contient l'ensemble des sons digitalisés de l'article dont nous aurons besoin.

Il vous faudra adapter les répertoires de dépôt en fonction de votre environnement. Essayons dans un premier temps de jouer un son à partir d'un fichier au format wav :

(SampledSound fromWaveFileNamed: '/home/messner/PharoSound/sound/mouette.wav') play

Le son est joué en tâche de fond sans bloquer le reste de notre environnement. Nous pouvons par exemple utiliser cette méthode pour sonoriser une action ponctuelle ou une alerte.

Si notre but était de concevoir un jeu, le chargement du son directement depuis le disque à des moments clés risquerait de ralentir les animations voire de le rendre injouable. Afin d'éviter ces petits désagréments, les sons seront chargés en mémoire et joués lorsque ce sera nécessaire pour gagner en temps de réponse.

Pharo met à notre disposition une banque de sons que nous pouvons alimenter suivant nos besoins. Pour rajouter un son à cette librairie, nous utiliserons le message addLibrarySoundNamed:samples:samplingRate:. Dans un workspace, copiez le code suivant et évaluez-le pour rajouter le sample dans la librairie :

| spl spl2 |

"Chargement du son à partir d'un fichier"

spl := SampledSound fromWaveFileNamed: '/home/messner/PharoSound/sound/guitar.wav'.

 

"Ajout du son dans la librairie pour le jouer plus tard"

SampledSound addLibrarySoundNamed: 'guitar'

             samples: spl samples

             samplingRate: spl originalSamplingRate.

Maintenant nous pouvons jouer le sample à la fréquence et à la durée souhaitée :

| spl spl2 |

spl := SampledSound soundNamed: 'guitar'.

spl2 := (SampledSound samples: spl samples samplingRate: 2000).

spl2 duration: 0.1.

spl2 play.

Nous entendons uniquement les premiers samples du fichier et à une fréquence très basse. Si nous augmentons samplingrate et duration nous pourrons entendre de plus en plus de samples jusqu'à ce que l'ensemble des données soit joué.

9. Un dernier petit effort

Nous allons clore cet article sur une dernière méthode permettant de jouer plusieurs sons digitalisés en séquence. Dans un premier temps nous allons charger en mémoire les différents samples qui seront utilisés.

| spl spl1 spl2 |

"Chargement des sons"

spl := SampledSound fromWaveFileNamed: '/home/messner/PharoSound/sound/ocean.wav'.

spl1 := SampledSound fromWaveFileNamed: '/home/messner/PharoSound/sound/mouette.wav'.

spl2 := SampledSound fromWaveFileNamed: '/home/messner/PharoSound/sound/bateau.wav'.

 

"Ajout des sons dans la librairie pour les jouer plus tard"

SampledSound addLibrarySoundNamed: 'ocean'

             samples: spl samples

             samplingRate: spl originalSamplingRate.

SampledSound addLibrarySoundNamed: 'mouette'

             samples: spl1 samples

             samplingRate: spl1 originalSamplingRate.

SampledSound addLibrarySoundNamed: 'bateau'

             samples: spl2 samples

             samplingRate: spl2 originalSamplingRate.

Maintenant que tous nos sons sont chargés, nous allons les jouer en séquence grâce à la classe QueueSound vue précédemment. Ils sont joués les uns après les autres à la fréquence souhaitée.

| mix q1 q2 spl1 spl2 spl3 |

 

q1 := QueueSound new.

q2 := QueueSound new.

mix := MixedSound new.

spl1 := (SampledSound samples: (SampledSound soundNamed: 'ocean')

samples samplingRate: 22000).

spl1 duration: 20.

spl2 := (SampledSound samples: (SampledSound soundNamed: 'mouette')

samples samplingRate: 22000).

spl2 duration: 10.

spl3 := (SampledSound samples: (SampledSound soundNamed: 'bateau')

samples samplingRate: 22000).

spl3 duration: 5.

 

q1 add:spl2;add:spl2.

q2 add:spl3;add:spl3.

mix add: q1 pan: 0 volume: 0.100;

add: q2 pan: 1 volume: 0.5;

add: spl1 pan: 0.5 volume: 1.

mix play.

Conclusion

Nous vous avons montré une petite partie des possibilités que Pharo offre pour la création et manipulation des sons. Nous espérons vous avoir donné envie d'expérimenter !