Comment générer du son depuis la lecture d’une image ou d’un dessin, un peu comme l’UPIC de Xenakis ? Mais nous commencerons par la petite porte: on va juste générer des fréquences à partir des valeurs d’une image.

 

Nous allons décortiquer une méthode simple permettant dans un premier temps de lire et d’interpréter une image noir et blanc de façon linéaire. Ici, il s’agira bien de générer du son plutôt que d’activer ou contrôler une source sonore depuis une action comme un geste de la main.

Ci-dessus, voilà ce qu’on arrivera à faire à la fin de cet article, soit générer du son depuis une image. Rien de très gracieux ici, mais c’est voué à être revu.

Avant de commencer à coder, résumons ce que nous voulons. Premièrement, nous voulons générer des sons à partir d’informations relatives à une image, et plus précisément d’une parcelle de cette image. On pourra sélectionner la zone soit avec la souris, soit avec un curseur qui s’incrémente en abscisse ou en ordonnée. Cette zone pourra être fine (1 pixels) ou bien une zone plus large (disons 20px de largeur, et toute la hauteur de l’image). Cette zone générera un seul son.

 

Il y a plusieurs façon de synthétiser un son à partir de plusieurs informations. Ici, nous allons dire qu’on analyse les valeurs de gris (=luminosité) d’une parcelle de l’image, on compte toutes les valeurs (de 0 à 255) de tous les pixels et on en fait une moyenne. À cette moyenne, on lui faire subir une petite série d’opérations mathématiques afin qu’on puisse la transformer en valeur en Hertz assez acceptable pour être utilisée. Par exemple, si une zone nous donne 8, on va devoir trouver des petites formules pour que cette valeur soit plus grande. Eh oui, un rappel, la tranche audible des fréquences va d’environ 20Hz à 20000Hz. Et encore, ça dépend si on est fatigué ou si notre ordinateur est capable d’émettre des sons très graves ou très aigus.

Bien. On va décider qu’on veut faire notre bidouillage à partir d’une image. Bien, allez, on l’affiche.

Capture d’écran 2015-07-07 à 18.10.23

Magnifique. Maintenant, nous voulons définir notre curseur qui délimite la zone d’analyse de l’image. Cette « zone » récupère les informations de l’image, donc, nous allons balayer toute la « zone » avec pixels et compter leurs luminosité. Nous pourrons donc opter pour la méthode qui consiste à créer une deuxième image « cobaye », qui copie une portion de l’image au même endroit ou elle se situe (oui…). L’intérêt de faire une deuxième image plus petite est qu’on peut parcourir l’ensemble de l’image de 0 jusqu’à son maximum avec une simple boucle… ce qui nous permet de ne pas trop avoir à bricoler un truc avec des opérations mathématiques un brin plus avancées sur l’ensemble des pixels de notre canevas (pour en retenir qu’une parcelle, pas fabuleux). Et ça permet aussi de pouvoir, dans un contexte orienté objet, de multiplier ces zones (et donc les sons).

Oulà, bon regardons le code pour comprendre mieux (voir ci-dessous. J’ai copié un carré de l’image) Faites attention aux commentaires que j’ai laissé dans le code.

Capture d’écran 2015-07-07 à 18.46.35

decalage

Détails : Maintenant, on enlève le décalage de + 10 (et j’enlève les commentaires maintenant que vous avez pigé… je crois). Nos pixels copiés forme une deuxième couche identique à la première. Pour délimiter cette zone, je place un petit carré de la même taille et la même position (avec rect() évidemment). J’ai fait une petite fonction carré, parce que je suis propre. Mais faites ce que vous voulez, hein. Bon.

Désormais, nous allons faire des opérations sur les pixels pour déterminer la moyenne de la luminosité de ce morceau d’image. Allez un petit loadPixels() sur notre zone pour pouvoir récupérer les infos des pixels avec… pixels[]. Ah oui, il faut faire une petite boucle pour parcourir l’ensemble de nos pixels. Comme d’hab. Oui, voilà, un truc comme ça:

Capture d’écran 2015-07-07 à 19.19.07

À l’intérieur de la boucle, on va créer une variable locale de type color qui va récupérer les informations colorimétriques brutes de tous nos pixels (on veut récupérer un flux d’informations « brut » juste ici. Donc du local ça suffit). Ensuite on va déclarer une variable globale (donc utilisable partout dans le programme) qui va accumuler les informations filtrés avec brightness() de notre variable brute. On va extirper seulement les valeurs relatives à la luminosité. Et on va mettre à jour tout ce bazar avec un updatePixels() de notre zone. Voyez plutôt:

Capture d’écran 2015-07-07 à 19.27.48

Les deux dernières lignes de code de l’image ci-dessus, vous l’avez peut-être compris, c’est juste une bête moyenne et un affichage console de cette valeur. Si on déplace notre souris, on peut voir les valeurs fluctuer.

valeurs

Bien, le plus gros a été fait, et ça a été simple! Les valeurs vont de 0 à 255 maximum. La tranche de fréquence audible pour un être humain va de 20 à 20000Hz. Maintenant vous savez (ou vous sentez) que pour transformer ces valeurs qui vont de 0 à 255, on va faire une petite règle de trois avec la fonction map(). Évidemment, on créera une variable pour faire cela.

Pour le son, on utilisera la bibliothèque Minim. Bon, je reviens pas sur l’utilisation de Minim, je vous laisse plutôt le code complet. À télécharger ici ou à lire ci-dessous.

import ddf.minim.*;
import ddf.minim.ugens.*;Minim minim;
AudioOutput out;
Oscil wave;PImage upic;
PImage zone;
float valeursNuances;
float toFrequence;void setup() {
upic = loadImage(« upic.gif »);
size(upic.width, upic.height);zone = createImage( 50, 50, RGB ); //Notre zone est un carré de 50*50minim = new Minim(this);
out = minim.getLineOut();
wave = new Oscil (440, 0.5f, Waves.SINE);
wave.patch( out );
}

void draw() {
image(upic, 0, 0); // original
zone.copy( upic, mouseX, mouseY, 50, 50, 0, 0, 50, 50);//copié…

image(zone, mouseX, mouseY);//affiché
carre();

// Analyse de notre zone
zone.loadPixels();
for (int i = 0; i < zone.pixels.length; i++){ color valeursBrutesPixels = zone.pixels[i]; valeursNuances += brightness(valeursBrutesPixels); } //zone.updatePixels(); //pas utile en fait valeursNuances = (float) valeursNuances / ( 50 * 50 ); toFrequence = map(valeursNuances, 0, 255, 20, 20000); //println(toFrequence); wave.setFrequency(toFrequence); } void carre(){ noFill(); stroke(255,255,0); rect(mouseX, mouseY, 50, 50); }

L’étape supérieure serait de pouvoir gérer la lecture d’images en couleurs depuis un flux webcam avec par exemple, un tête de lecture automatisée. On pourrait faire un synthétiseur ou un loopeur contrôlé avec une feuille blanche et des feutres.