Ecole des Mines de Nancy

Cours "Introduction à l'informatique"

Projet Java
traitement du son
Séance 1 - vendredi 19 mai 2006


Voici un fichier son wave: auprintemps.wav.

But de ce tp: répondre aux questions suivantes en complétant le code fourni.


Question 0

Commencez par jouer auprintemps.wav sur la carte son avec votre programme.


Question 1

Ecrivez une fonction permettant de sommer deux signaux.


Question 2

Ecrivez une fonction permettant de multiplier un signal par une constante.


Question 3

Ecrivez une fonction permettant de générer un signal sinusoïdal dont on précise la fréquence et l'amplitude.


Question 4

Ecrivez une fonction permettant de concaténer deux signaux.


Remarques:
1) faites des expériences avec vos fonctions: écoutez vos sinusoïdes, mixez-les, manipulez auprintemps.wav...
2) il serait mieux (pourquoi ?) de faire toutes les opérations en considérant les voix gauches et droites comme des tableaux de double et non d'int, et faire la conversion au dernier moment. Faites-le en modifiant Sound.java.





Séance 2 - vendredi 2 juin 2006


Question 0

Terminez les questions de la séance précédente.


Un signal sonore s est représenté par N échantillons. On supposera ce signal périodique (de période N). Il est possible de l'interpoler par un polynôme trigonométrique sous la forme:
s(n) = 1/N \sum_{k=0}^{N-1} S(k) * exp(2*i*pi*n*k/N)
où :
S(k) = \sum_{n=0}^{N-1} s(n) exp(-2*i*pi*k*n/N)
La suite obtenue S est appelée transformée de Fourier du signal s.
La formule pour calculer s à partir de S est appelée transformée de Fourier inverse.


Question 1

Que dire de la transformée de Fourier S si le signal s est réel ? (ce qui est notre cas)
Plus précisément, quel est le conjugué de S(k) ?
On suppose que N est impair (pour des raisons de commodités d'écriture).
Montrez que:
s(n) = 1/N * [ S(0) + 2 * \sum_{k=1}^{(N-1)/2} Re( S(k)exp(2*i*pi*n*k/N) ) ]
A quoi correspond S(0) ? A quels termes correspondent les hautes fréquences du signal ?

Ecrivez une fonction qui calcule la transformée de Fourier d'un signal, et une autre qui calcule la transformée de Fourier inverse.

Vérifiez (en écoutant le signal par exemple) que la composition des deux opérations est bien l'identité (aux erreurs d'arrondis près).


Question 2

Comment se transforme le signal sonore lorsque vous enlevez les termes correspondant aux hautes fréquences ou aux basses fréquences dans la transformée de Fourier ?


Question 3

On va synthétiser des signaux bruités à partir de "auprintemps.wav" (ou tout autre fichier wav de votre choix).
Nous nous intéresserons à deux grandes familles de bruit:
- bruit impulsionnel: on remplace une proportion p des échantillons par une valeur tirée uniformément dans un certain intervalle.
- bruit gaussien additif: on ajoute à chaque échantillon une valeur donnée comme réalisation d'une variable aléatoire gaussienne.

Créez deux fonctions permettant de bruiter un signal (bruit impulsionnel ou bruit gaussien). La classe Random permet de générer des nombres aléatoires, uniformément ou selon une distribution gaussienne.

Remarque
: une variable aléatoire gaussienne Y de moyenne m et d'écart-type v est obtenue à partir d'une variable aléatoire gaussienne X de moyenne 0 et variance 1 par: Y=m+v*X.

Ecoutez les signaux bruités avec vos fonctions pour différentes valeurs des paramètres.


Question 4

On s'intéresse dans cette question à des filtres permettant de "débruiter" le signal sonore. On considérera deux familles de filtres.
- filtres linéaires: on remplace un échantillon du signal par une combinaison linéaire (dont la somme des poids vaut 1) d'échantillons voisins. Il s'agit d'une convolution du signal.
- filtres médians: on remplace un échantillon du signal par la valeur médiane des échantillons voisins.

Écrivez des fonctions permettant de calculer le signal filtré, en particulier en fonction de la fenêtre d'analyse.

Sachant que F(s*n) = F(s).F(n) (où s désigne le signal, n le noyau de convolution, * la convolution circulaire, . le produit terme à terme, et F la transformée de Fourier), quel effet à une convolution par un noyau gaussien ou un noyau uniforme sur les fréquences du signal original ?
On rappelle que la transformée de Fourier d'une gaussienne de variance v est une gaussienne de variance proportionnelle à 1/v.

Expérimentez le débruitage avec ces noyaux.





Séance 3 - vendredi 9 juin 2006


Question 1

Dans cette première question, vous allez réaliser un traceur de "fonctions" pour visualiser le spectre de vos signaux.

Créez une classe Graphe qui étend JPanel:
public class Graphe extends JPanel
(il faut importer java.awt.Graphics et javax.swing.JPanel)
Le constructeur de cette classe admet un tableau unidimensionnel de doubles (qui correspond à la liste des valeurs que l'on veut représenter). Ce tableau est stocké dans un attribut tab de la classe Graphe (qui sera un tableau de même taille).
Ensuite on écrit (surcharge) la méthode paintComponent de prototype:
public void paintComponent(Graphics g)
de manière à tracer le graphe.
Il suffit de tracer des lignes entre les points de coordonnées (i,tab[i]) à l'aide de la méthode drawLine:
g.drawLine(a,b,c,d); permet de tracer un segment entre les points (pixels) de coordonnées (a,b) et (c,d).
Ici getSize().width et getSize().height donnent les longueurs et largeur du JPanel sur lequel on trace le graphique.
Il faudra faire attention au fait que l'axe des ordonnées est ici inversé par rapport à un graphe de fonction classique... Comment inverser les axes ?
Voilà pour la classe Graphe.

Cette classe s'utilise dans le programme principal à l'aide de la suite d'instructions:

Graphe fig = new Graphe(tab_a_tracer); // tab_a_tracer est un tableau de doubles
JFrame frame = new JFrame("Mon graphique");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // exit lorsque l'on ferme la fenetre
frame.getContentPane().add(fig);
frame.setSize(400, 300); // taille de la fenetre, que vous pouvez redimensionner
frame.setVisible(true);

Ecrivez donc une fonction TracerGraphe prenant en argument un tableau de double et traçant le graphique correspondant.
Testez.


Question 2

Vous avez pu vous rendre compte lors de la séance précédente (question 1) que l'implantation naïve de la transformée de Fourier est inutilisable dans la pratique (ie pour des sons un peu longs). Elle nécessite de l'ordre de O(N^2) opérations, où N est la longueur du signal analysé. On dispose de la transformée de Fourier rapide (FFT), qui nécessite de l'ordre de O(N.log(N)) opérations. (Voir les explications au tableau)
Voici les fonctions qui correspondent à la transformée de Fourier rapide (fft) et à la transformée de Fourier inverse rapide (ifft). La transformée de Fourier inverse se déduit facilement de la transformée de Fourier par conjugaison.

Attention: les fonctions qui implémentent la fft et la ifft nécessitent des signaux dont la taille est une puissance de 2. Modifiez le signal analysé de manière à satisfaire cette condition (en ajoutant des 0) avant d'appeler fft et ifft.
D'autre part, les coefficients du signal et de la transformée de Fourier sont supposés complexes: ils sont représentées comme des double[N][2]: modifiez donc votre code pour rester compatibles avec ce format de données.

On remarque que l'implentation proposée n'est pas très efficace du point de vue de la gestion de la mémoire (pourquoi ?) et ne permet pas de traiter des signaux trop longs. On se contentera de faire l'analyse du signal sur des morceaux de 4096 échantillons mis bout-à-bout.

A quoi ressemble le graphe des modules des coefficients de Fourier d'un signal du genre sin(a*2*pi/4096) (si le signal a pour longueur 4096) ?

Reprenez les questions 1 et 2 de la séance 2 avec les fonctions fft et ifft, en découpant le signal en morceaux de 4096 échantillons.

Vous pouvez expérimenter en traçant les graphes des modules des coefficients de Fourier, ou aussi en comparant le graphe du signal original et celui du signal modifié.


Question 3

Dans le prolongement de la question 4 de la séance 2, expérimentez les effets de la convolution du signal par des filtres laissant passer les basses fréquences (dits filtres passe-bas) du type [1 1 1]/3 ou les hautes fréquences (dits filtres passe-haut) du type [-1 0 1]/2.
Faites des essais sur des signaux de différentes natures (voix, musique...), et ajoutez du bruit plus ou moins fort.




Séance 4 - vendredi 16 juin 2006


Voici la correction de la question 1 de la séance 3: une classe Graphe.java et une fonction à ajouter à votre code pour utiliser cette classe. Cette fonction permet de tracer le graphe représentant un tableau de doubles (si vous travaillez avec des tableaux d'entiers, il suffit de modifier l'appel à la fonction afficher ainsi que le constructeur de Graphe.java).
Attention à commenter dans votre code les lignes relatives à l'envoi du son sur le casque audio (surtout la ligne System.exit(0); ) sinon rien n'est affiché...

Remarque: la question 3 est indépendante des questions 1 et 2.


Question 1

On reprend ici la question 2 de la séance précédente. Il s'agit d'utiliser la transformée de Fourier rapide. Les fonctions fft et ifft fournies admettent comme argument des tableaux à deux lignes, qui correspondent respectivement aux parties réelles et imaginaires. Pour calculer la fft d'un signal (réel), il faut donc d'abord le copier dans un tableau à deux lignes dont les éléments de la deuxième ligne sont nuls.

A quoi ressemble le graphe des modules des coefficients de Fourier d'une sinusoïde ?

Correction:

int N = 256; // la taille du signal est bien une puissance de 2
double[][] signal = new double [N][2];
double[] signalbis = new double [N];
      
for (int i=0; i<N; i++) {
    signal[i][0] = (double)Math.sin(8*2*i*Math.PI/N); // creation de la sinusoide
    signalbis[i] = signal[i][0]; // creation du tableau unidimensionnel associe
}
       
double[][] fsignal = fft(signal); // calcul de la transformee de fourier
       
double tab[]=new double[N];
for (int i=0;i<N;i++)
    tab[i]=fsignal[i][0]*fsignal[i][0]+fsignal[i][1]*fsignal[i][1];  // carre du module
       
afficher(tab); // affichage du signal: agrandissez la taille de la fenetre
afficher(signalbis);  // affichage du module de la transformee de Fourier: agrandissez

Fin de la correction

Vous observez deux pics sur le module de la transformée de Fourier. Est-ce normal ?
Rappel: sin(x) = [exp(ix) - exp (-ix)] / (2i) ...


Question 2

On considère le signal issu du fichier auprintemps.wav (ou tout autre fichier wav échantillonné sur 44.1kHz et 16 bits, à moins que vous ne modifiez les paramètres adéquats dans les routines de lecture...)
Vous appliquerez les traitements suivants sur des morceaux successifs de taille N=4096 (pour des raisons de débordement mémoire dans le calcul récursif de la fft).

Annulez (ou diminuez) les coefficients de la transformée de Fourier qui correspondent au basses fréquences. Ces coefficients sont répartis symétriquement autour du coefficient d'indice 0 (car la transformée de Fourier est périodique), donc annulez les coefficients d'indices 0, 1, N-1, etc).
Qu'entendez-vous ?
Faites de même pour les hautes fréquences, réparties symétriquement autour du coefficient d'indice N/2.


Question 3

On reprend la question 3 de la séance 3 (indépendant de la fft, en particulier il est inutile de découper le signal en tranches de taille 4096).

Effectuez une convolution du signal sonore par [1 1 1]/3.
Ceci signifie que vous créez un nouveau signal s2 à partir du signal s1:
s2[i] = ( s1[i-1] + s1[i] + s1[i+1] ) / 3;
Qu'entendez-vous ? Que se passe-t-il si la taille de la fenêtre de convolution augmente ? (par exemple taille 7, 21, etc au lieu de 3) Expérimentez.

Effectuez une convolution du signal sonore par [-1 0 1]/2.
Cette fois: s2[i] = ( s1[i-1] - s1[i+1] ) / 2;
Mêmes questions.


Question 4

Reprenez les questions 2 et 3 avec un signal d'entrée bruité (cf question 3 de la séance 2).
Expérimentez aussi le filtre médian (question 4 de la séance 2).



A l'issue de ces quatre séances, merci de m'envoyer un jar de l'ensemble de votre oeuvre !




Lectures conseillées pour aller plus loin:
C. Gasquet et P. Witomski, Analyse de Fourier et applications, Masson, 1990.
Tout cours de mathématiques du signal.

Les fichiers auprintemps.wav et Sound.java sont fournis par Guillaume Bonfante.



Contact:
Frédéric Sur, ATER au département d'informatique.