Biblio-théque interne et le son

tutorial de programmation
en processing

dessein

Bibliothèque intégré & Minim

Nous aborderons dans ce cours une bibliothèque qui s'appelle "Minim" qui permettra de jouer, créer, enregistrer du son et le capter avec un microphone. Nous donnons également quelques notions de base du son numérique ainsi qu'un brève explication de la fonction de l'interface dans l'OOP. Un dernière paragraphe présentera une objet qui permet la détection des 'beats' dans un morceau de musique.

 

Bibliothèque

Processing a été développé à l'origine pour développer facilement des graphismes statiques et dynamique ainsi que de pouvoir récupérer des inputs de la souris ou du clavier. Cette réduction volontaire rend Processing simple pour débuter. Mais son architecture (Java) lui permet d'élargir ses capacités très facilement. A travers l'importation des bibliothèques (libraires en anglais) on peut communiquer à travers différents protocoles de communication avec d'autres ordinateurs (midi, osc...), exporter des graphismes sous différents formats (pdf, dxf...), travailler en 3D, intégrer du son, ou encore traiter des images vidéos et faire plein d'autres choses.

Les bibliothèques de Processing

 

Core libraires et contributions.

On voit sur la page de libraires qu'il y a deux sections. La première concerne les bibliothèques qui sont intégré dans Processing, comme OpenGL ou Minim pour le son. Mais l'architecture Open Source de Processing ainsi que la OOP de Java permet et facilite l'intégration d'autres bibliothèques qui sont en effet des classes (cf. cours sur les classes). Ainsi la communauté de Processing qui a développé énormément des bibliothèques, appelé contributions, et qui se trouvent dans la deuxième section. Les core libraires sont pour la plus part du temps documenté sur le site de Processing, les 'contributions' sont documenté sur le site de ses auteurs.

 

La bibliothèque du son Minim

La bibliothèque de Minim fait parti des ceux qui sont intégré dans le logiciel de Processing depuis la version 1.0. C'est en effet un ancienne 'contribution' écrit par Damien Di Fede qu'on télécharge maintenant par défaut avec Processing, mais sa documentation se trouve toujours sur le site de son auteur. Pour utiliser la librairie, il faut effectué quelques étapes de routine qui sont toujours les mêmes.

 

Importation & instanciation

Pour utiliser les libraires dans le sketch, il faut d'abord l'importer. Ceci se fait simplement par une ligne de code. Soit on l'écrit directement dans le code ou on passe par le menu "Sketch" -> "Import Libraires" -> le nom de la bibliothèque.

import ddf.minim.*; 

La commande import désigne que les classes de la bibliothèque en question peuvent être utiliser; l'astérisque désigne que tous les classes de minim sont chargé.

On déclare d'abord un variable de type Minim (cf classes) et ensuite on instancie un objet Minim avec l'argument "this".

import ddf.minim.*;
Minim minim;
void setup() {
size(100, 100);  
minim = new Minim(this);
}
void draw() {
// le code....
}

Le basique: Jouer, créer, enregistrer et écouter du son

Une fois l'objet de minim est crée, on peut manipuler le son des différentes manières. Chaque manipulation correspond à un (ou plusieurs classes):

Jouer un morceau avec Audio Player et fermer application

Trois classes différents nous permettent de jouer un fichier audio. Nous verrons encore quelques 'routines' à faire à travers l'exemple d' AudioPlayer. Les autres classes fonctionnera quasi de la même manière.

D'abord il faut créer un objet d'AudioPlayer
AudioPlayer player;

Au lieu d'instancier l'objet avec le mot clé new, nous l'instancions à travers la méthode "loadFile" de la classe minim. Dans le cours Blob détection, il y a une explication plus détaillé de l'instanciation à travers la méthode d'un autre classe. L'argument de la méthode et un String qui correspond au nom du fichier qui se trouve dans le dossier 'data' du sketch. Nous connaissons ce genre de procédure de traitement d'image.

player = minim.loadFile("roboter.mp3");
// pour télécharger le morceau mythique Roboter de Kraftwerk, c'est ici

Pour fermer proprement les instances qu'on a crée, il faut rajouter à la fin une méthode "stop" qui ferme chaque classe qui a été instanciée et arrêter l'objet minim. La raison est que chaque objet audio est traité dans un thread différent.

void stop(){
player.close();
minim.stop();
super.stop(); }

Il faut encore rajouter la méthode play(), et enfin Processing fait du bruit!
player.play();

Voici le code complet:

import ddf.minim.*;
Minim minim;
AudioPlayer player;

void setup(){
minim = new Minim(this);
player = minim.loadFile("roboter.mp3");
player.play();
}
void draw(){
}

void stop(){
player.close();
minim.stop();
super.stop();
}

Il a également deux autres classes qui permet de jouer du son: AudioSnippet (Fragment d'audio) et Audio Sample (Échantillon Audio).

La principale différence d'AudioSnippet est que le morceau n'est pas joué en streaming, c'est-à-dire que le morceau est chargé au fur et à mesure dans la mémoire, mais elle est chargé complètement avant d'être jouer (playback). Ceci peut poser des problèmes quand le morceau est lourd. Attention aux mp3, même si son poids initial n'est pas énorme, il est décompréssé au moment de sa lecture et son poids devient vite 10x plus.

AudioSample est conçu pour des petits événements sonores, comme par exemple le bruit dans un jeu vidéo. Elle fonctionne comme AudioSnippet mais elle permet de modifier les deux canaux stéréo car l'audio est stocké sous forme des floats normalisé (-1, 1). Il est possible de charger les floats dans un liste avec la méthode getChannel(BufferedAudio.RIGHT);

Pour aller plus loin: AudioPlayer
  AudioSnippet
AudioSample

Le son numérique

Avant de continuer, un petit rappel de ce qui est le son physiquement:

Le son est une onde produite par la vibration mécanique d'un support fluide ou solide et propagée grâce à l'élasticité du milieu environnant.

La fréquence d'un son est exprimée en hertz (Hz), elle est directement liée à la hauteur d'un son perçu, mais n'en est qu'une des composantes. À une fréquence faible correspond un son grave, à une fréquence élevée un son aigu.

L'amplitude est une autre caractéristique importante d'un son. L'intensité perçue dépend (entre autres) de l'amplitude : le son peut être fort ou doux (les musiciens disent forte ou piano). Dans l'air, l'amplitude correspond aux variations de pression de l'onde.

Le timbre est ce qui peut identifier un son d’une façon unique. Deux sons peuvent avoir la même fréquence fondamentale et la même intensité; mais ne peuvent jamais avoir le même timbre.

Le audio numérique est le son transformé en un format numérique. Un signal analogique est convertit vers un signal numérique à une fréquence d'échantionnage et une 'bit résolution' données. On peut dire que le sample est la plus petit dénominateur du son numérisé. Mais attention, on utilise le terme sample aussi pour designer un petit son enregistré. Ces homonyme sont aussi utilisé dans la bibliothèque Minim ou l'objet AudioSample désigne un son courte, par contre quand il s'agit du Samplingrate, on parle de la fréquence par la quelle le son est enregistré. Ce deux significations sont complètement différents et n'ont rien avoir l'un avec l'autre.

La fréquence d'échantillonnage (Samplingrate) est une donnée essentielle pour la qualité du son numérique. Avec la quantification des échantillons, elle détermine non seulement la qualité de l'enregistrement, mais encore la place que le fichier audio occupe en mémoire. La fréquence d'échantillonnage s'exprime en hertz (Hz) et détermine le nombre d'échantillons utilisés par seconde.

La bit depth ou 'bit résolution' décrit le nombres des bits d'information enregistré pour chaque échantillon. Le CD a un 'bit dépit' de 16bits et le DVD 24 bits.

Plus la qualité d'enregistrement est grande, plus le fichier audio occupe de l'espace. La différence entre une quantification sur 8 bits et une sur 16 bits n'est pas facilement perceptible pour une oreille humaine non exercée. En revanche la fréquence d'échantillonnage a des effets très sensibles.

Ainsi une fréquence de 11 kHz (11025) se révèle suffisante pour l'enregistrement de la parole, mais elle ne convient pas pour la musique car cela revient à écouter une symphonie au téléphone. La haute fidélité propose de restituer les fréquences inférieures à 22 kHz. C'est en effet la limite de l'audible pour l'oreille humaine.

(extraits de Wikipedia)

 

 

Audio-output: créer un son synthétisé

AudioOutput permet de synthétisé du son en temps réel. Pour faire ceci, il faut d'abord intégré deux nouveaux 'packages' dans notre sketch. En effet, quand nous écrivons la ligne

import ddf.minim.*; 


nous importons les classes principales de la bibliothèque. Ces classes sont organisées dans une sorte des dossiers, qui rangent les classes java selon des groupes thématiques. Mais la library de Minim n'est pas seulement composé d'un seul 'package' mais bien plusieurs, notamment un pour les effets et l'autre pour les signaux.

ddf.minim.signals.*;
ddf.minim.effects.*;

Pour produire du son dans processing, il faut s'imaginer de créer d'abord un câble entre le programme et la carte de son ce qu'on appelle un 'line'. Après avoir crée l'objet AudioOutput, nous allons l'instancier par la méthode getLineOut, ses trois premiers arguments sont: Minim.Stereo (/-Mono) taille du buffer en int (par défaut 1024) et la fréquence d'echantionnage qui est par défaut 44100 Hz.

Minim minim;
AudioOutput out;
void setup()
 minim = new Minim(this);
out = minim.getLineOut(Minim.STEREO, 2048, 22050);

Une fois le câble est créer, nous pouvons envoyer des sons synthétisé dans ce câble, la bibliothèque nous propose une série des oscillateurs standard et des bruits, tel que le

Oscillateurs PulseWave, SawWave, SineWave, SquareWave, TriangleWave,
Bruits WhiteNoise, PinkNoise


Il faut préciser pour chaque oscillateur sa fréquence. Pour l'orientation; le 'La' au milieu d'un piano et aussi le son du téléphone (avant les lignes numériques ;-( est du 440Hz. Une octave plus haut est 880Hz, plus bas 220Hz et ainsi de suite. Il faut également préciser l'amplitude entre 0 (silence) et 1 (max) et à nouveau la fréquence d'echantionnage ( par défaut 44 100) ou utiliser celui de l'objet d'AudioOutput..

Le WhiteNoise et le PinkNoise ne sont pas des oscillateurs mais ils produisent des bruits (beaucoup de fréquences différents au même temps). Le whiteNoise est le son de la neige de télé. Il réunit tous les fréquences au même temps. Pour les 'noises' il ne faut évident pas préciser la fréquence.

Line peut aussi contrôler les sons par les paramètres suivants: pan, gain, volume, muet, and balance, sampleRate.

import ddf.minim.*;
import ddf.minim.signals.*;Minim minim;
AudioOutput out;
SineWave sine;

void setup(){
size(512, 200, P3D);
minim = new Minim(this);
out = minim.getLineOut(Minim.STEREO, 1000);
sine = new SineWave(240, 0.5, out.sampleRate());
out.addSignal(sine);
stroke(255);
}
void draw(){
background(0);
float freq = map(mouseY, 0, height, 880, 60);
float amp = map(mouseX, 0, width, 0.01, 1);
sine.setFreq(freq);
sine.setAmp(amp);
//out.setGain(map(mouseX, 0, width, -1, 1));// active le et desactive amp
for(int i = 0; i < out.bufferSize() - 1; i++)
{
line(i, 50 + out.left.get(i)*50, i+1, 50 + out.left.get(i+1)*50);
line(i, 150 + out.right.get(i)*50, i+1, 150 + out.right.get(i+1)*50);
}
}
void stop()
{
out.close();
sine.close();

minim.stop();
super.stop();
}

Pour visualiser le resultat, nous avons utiliser la méthode ou.left.get() et out.right.get(). Elles nous retournent une valeur entre 1 et -1 qui est l'amplitude du canal gauche et droite à une moment donné. Pour pouvoir le visualiser, nous l'avons amplifié par 50. Comme notre fréquence d'échantionnage est de 1024, l'ordinateur produira 1024 samples ou bien de petits bouts de son par frame. Dans le boucle, on découpe le signal en 1024 morceau.

Pour aller plus loin: audioOutput
  Oscillator
  Noise
  AudioSource
  Controller

 

AudioInput: le microphone

AudioInput ressemble beaucoup à AudioOutput. Mais au lieu de créer un 'cable' du programme à la carte de son, nous créerons un câble dans l'autre sens de la carte, de l'entrée activé au programme. Pour entendre quelque chose, il faut bien sure branché le microphone et l'activer s'il n'y a pas une déjà intégré. Chaque système d'exploitation à sa manière de le faire, sur PC, il faut ouvrir l'interface de la carte graphique et décocher 'muet' pour le microphone.

La principale différence avec AudioOutput est que le l'objet s'instancie avec getLineIn, les arguments sont les mêmes, et ils sont optionnels. (type = minim.STEREO /MONO )

AudioInput in;
in = minim.getLineIn(int type, int bufferSize, float sampleRate, int bitDepth)

Pour aller plus loin: audioInput

 

 

AudioRecorder

L'objet AudioRecorder fait exactement ce qu'on attend, il enregistre dans un fichier un source audio. Cette source audio peut être à l'origine de tous les objets que nous avons abordé, tel que le AudioPlayer, AudioSample, AudioInput, AudioOutput à l'exception de AudioSnippet.

L'instanciation de l'objet se fait avec les trois arguments:

AudioRecorder recorder;
recorder = minim.createRecorder(source, String nomdeFichier, boolean buffered)

Pour aller plus loin: audioInput

 

Interface

Le concepteur de Minim a fait recours au interface dans la construction. Le terme "interface" est utilisé aujourd'hui dans tous les domaines et dans tous les sens. Dans Java, elle désigne un type de référence, similaire a un classe.

D'une manière générale, l' interface sert à construire une architecture de programme. Elle facilite la conception des OOP.

Contrairement à une classe traditionnelle, on définit dans une interface uniquement les en-têtes ou signature des méthodes. Par exemple

interface Inter {
void premierMethode();
int deuxiemeMethode(float a);
}

L' interface ressemble donc à une classe, mais son mot clé est 'interface' est les méthode ne comportent pas du code ni des accolades.

L'intérêt des interfaces est d'être implementé par une ou plusieurs classes. Si une classe implemente un interface, elle est obligé d'avoir les mêmes méthodes que ceux définit dans la classe. C'est là tout son intérêt.

class CestClass implements Inter{
void premierMethode(){
code bla bla;
}
int deuxiemeMethode(float a){

code bla bla bla;
}

Les classes peuvent aussi implementer plusieurs classes.

class CestClass implements Inter, Interfu{
....
}

Minim a définit 4 interfaces qui regroupe des fonctions qui concernent plusieurs classes. Par exemple, l'interface Playable définit plusieurs en-têtes de méthode comme par exemple play() et pause(). Tous les classes qui implements l'interface Playable ont alors cette fonction. Il n'est pas difficile à deviner quel sont les classes concerné: AudioPlayer et AudioSnippet. AudioSample n'a pas implementé l'interface Playable, car l'objet est prévu pour des son très court. D'autres interfaces sont Recordable, Effectable, Polyphonic.

 

Pour aller plus loin: Interface
  Playable
  Recordable
  Effectable
  Polyphonic

 

Analyse du son: BeatDetect

Minim intègre également deux objets d'analyse audio, le FFT et le BeatDetect. Ce dernier analyse le changement d'energie du son qu'il détermine comme 'beat'. Il y a deux modes de détection, le sound energy mode et le frequency energy mode. Le principale différence est que l'energy mode détecte un pic (spike) dans le buffer et le frequency mode detect un pic dans un certain spectrum défini. Lors de l'instanciation de l'objet BeatDetect, le buffer rate et le sample rate doit être définit (normalement 1024, 44100).

Le principale méthodes sont:
void detect ( AudioBuffer ab); // left, right ou mix
boolean isOnset(); // return true si un beat is detecté
boolean isOnset (int i) // pour la frequency mode: i est la 'frequency band' (max 26?)
boolean isKick, isHat, isSnare quelques fréquence pour certains instruments sont préétabli:pour la kick drum, hi hat et snare drum
boolean isRange(int low, int high, int threshold) permet de définir un plage, attention la valeur low > hight.

Un exemple simple d'un energy beatDetect

import ddf.minim.*;
import ddf.minim.analysis.*;

Minim minim;
AudioPlayer song;
BeatDetect beat;
int x;

void setup(){
size(200, 200);
minim = new Minim(this);
song = minim.loadFile("roboter.mp3", 2048);
song.play();
beat = new BeatDetect();
ellipseMode(CENTER);
smooth();
x = 0;
}

void draw()
{
background(0,5);
beat.detect(song.mix);
float a = map(x, 20, 80, 60, 255);
fill( 255, 0,0);
if ( beat.isOnset() ) x = 80;
ellipse(x, x, 20, 20);
ellipse(width-x, x, x, x);
ellipse(x, height-x, x, x);
ellipse(width-x, height- x, 20, 20);
x *= .95;
}

void stop() {
song.close();
minim.stop();
super.stop();
}

Pour aller plus loin: BeatDetect
  FFT

 

Exercice