Projet Java Traitement du son – égaliseur
Transcription
Projet Java Traitement du son – égaliseur
Cours "Introduction à l’informatique" 6 juin 2008 Projet Java Traitement du son – égaliseur Le but des séances thématiques vise à réaliser une application permettant de lire des fichiers audio et d’y appliquer des transformations basiques (égaliseur, effets, ...). Dans un premier temps, on travaille sur le modèle (opérations sur les fichiers audio). Dans la dernière séance, on mettra au point l’interface graphique (vue et contrôleur). Séance 1 Voici quelque fichiers son wave : auprintemps.wav http://www.loria.fr/ ~canon/java/auprintemps.wav, bo.wav http://www.loria.fr/~canon/java/ bo.wav, salmonDance.wav http://www.loria.fr/~canon/java/salmonDance. wav. But de ce tp : répondre aux questions suivantes en complétant le code fourni Sound.java. Question 1 Commencer par jouer auprintemps.wav sur la carte son avec votre programme. Il s’agit simplement de tester votre configuration et d’importer correctement les fichiers son (import -> file system -> fichiers à importer). Question 2 Écrire une fonction permettant de sommer deux signaux. Il est impératif de ne pas comprendre les détails du fonctionnement du programme pour cela. Il suffit de savoir qu’un signal est encodé dans un tableau d’octets (byte) et qu’il est nécessaire d’en extraire les deux composantes (voix droite et gauche) pour le manipuler. Ensuite, chaque élément du tableau représente une valeur qui sera transmise à la carte son. Pour l’addition, il vous faut donc décomposer le signal 1 (en voix droite et gauche), additionner les deux tableaux (valeur par valeur) et reformer le tableau de byte initial (grâce à la primitive mix ). La méthode doit donc prendre deux arguments (deux tableaux de bytes) et renvoyer un tableau de même type. S’inspirer des signatures des fonctions existantes dans le code. Question 3 Écrire une fonction permettant de multiplier un signal par une constante (on augment ainsi l’amplitude du signal et donc le volume du son). Question 4 Écrire une fonction permettant de générer un signal sinusoïdal dont on précise la fréquence et l’amplitude (utiliser la méthode Math.sin http: // java. sun. com/ j2se/ 1. 4. 2/ docs/ api/ java/ lang/ Math. html# sin( double) ). Pour préciser la fréquence, il faut partir des donnés suivantes : il y a 44100 échantillons par seconde et la période d’un sinus est 2π. Le spectre audible se situe entre 200 Hz et 20k Hz. Question 5 Écrire une fonction permettant de concaténer deux signaux (concaténer signifie que l’on juxtapose l’un après l’autre les deux signaux). Question 6 Réaliser les effets écho et chorus décrits sur cette page http://en.wikipedia. org/wiki/Sound_effect#Techniques. Écrire pour cela deux fonctions en précisant la durée du décalage et le nombre d’écho en argument. Remarques : 1. faites des expériences avec vos fonctions : écoutez vos sinusoïdes, mixez-les, manipulez les fichiers sonores. . . 2. il serait mieux (pourquoi ?) de faire toutes les opérations en considérant les voix gauches et droites comme des tableaux de doubles et non d’entiers, et faire la conversion au dernier moment. Séance 2 Avant d’entamer cette partie, il faut finir au moins un effet sonore parmi les suivants : addition d’un signal sinusoïdal à un signal sonore lu, chorus, écho, inversion du signal (lecture arrière), . . . Il sera possible de compléter la liste des effets implémentés par la suite. 2 Fig. 1 – Signal temporel Question 1 Récupérer la bibliothèque numérique flanagan sur cette page http://www. ee.ucl.ac.uk/~mflanaga/java/. Elle vous permettra de réaliser la FFT. Lire la documentation associée http://www.ee.ucl.ac.uk/~mflanaga/java/FourierTransform. html (partie constructor et méthodes setData, getTransformedDataAsComplex, transform et inverse). Vous aurez également besoin de la classe Complex http: //www.ee.ucl.ac.uk/~mflanaga/java/Complex.html et des méthodes timesEquals et getReal. Pour importer le jar externe au projet : Project -> Properties -> Java Build Path -> Libraries -> Add Externel Jar. Un signal sonore s est représenté par N échantillons. On supposera ce signal périodique (de période N). On calcul sa transformé de Fourier S par : S(k) = N −1 X s(i) exp( i=0 −2iπki ) N On passe donc du signal temporel (figure 1) au spectre fréquentiel (figure 2) : Que dire de la transformée de Fourier S si le signal est réel (ce qui est notre cas) ? Plus précisément, quel est le conjugué de S(k) ? À quoi correspond S(0) ? Question 2 Réaliser la transformé de Fourier d’un signal (voix droite et gauche) puis calculer la transformé inverse et vérifier que cette composition des 2 fonctions respecte bien l’identité (en écoutant le signal par exemple). La procédure générale consiste à d’abord convertir les tableaux d’entiers en tableaux de complexes 3 Fig. 2 – Spectre fréquentiel puis à calculer la transformé de Fourier. On réalise ensuite l’opération inverse, en calculant la transformé inverse puis en convertissant le tableau de complexes en tableau d’entiers. Question 3 À partir de ce qui vient d’être réalisé, il est désormais possible de réaliser des opérations sur le spectre fréquentiel du signal. Il est rappelé que le spectre obtenu couvre les fréquences jusqu’à la fréquence de 22,05kHz (ce qui est la moitié de la fréquence d’échantillonage). Réaliser une fonction qui prend un tableau d’octets en argument et retourne un tableau de type similaire dont les signaux sonores contenus ne contiennent pas de fréquence supérieur à 1kHz. Tester en écoutant successivement le signal initial, puis celui altéré. Question 4 Réaliser une fonction qui égalise le spectre fréquentiel suivant des valeurs données (comme les égaliseurs de lecteur multimédia standard). On considère donc plusieurs plages de fréquences et pour chacune, on définit un coefficient qui sera appliqué à toute les valeurs de la transformé de Fourier afin d’amplifier ou d’atténuer certaine composantes du signal. Il sera par exemple possible d’ajouter un signal sinusoïdal au signal sonore puis de filtrer la plage de fréquences dans laquelle se situe le sinus afin de restaurer en partie le signal d’origine. Question 5 Revenir sur la séance précédente et finir les effets qui vous semblent les plus intéressants. Ne pas hésiter à en proposer d’autre. 4 Fig. 3 – Interface et fonctionnalités minimales Nettoyer le code de manière à disposer d’un maximum de fonctions travaillant sur des tableaux de bytes (en les convertissant en interne en tableau de réels). Améliorer l’égaliseur en proposant des plages de fréquence non-linéaire (10100, 100-500, 500-1500, 1500-7000, 7000-20000, par exemple) soit de façon prédéfinie, soit logarithmiquement. Proposer une solution pour que la pondération des fréquences voisines de plages distinctes soient moins abruptes. Découper le signal pour réaliser la transformé morceau par morceau et gagner ainsi en temps de traitement. Séance 3 Remarques Eclipse limite la quantité de mémoire disponible et cela peut entraîner des erreurs à l’exécution. Une exception en rapport avec le heap space est alors levée. Pour contourner le problème, il faut ajouter un argument : Run -> Run... -> Onglet “Arguments” -> VM arguments -> ajouter “-Xmx512M” (sans les guillemets). Une nouvelle classe Sound Sound.java est disponible. Elle permet de stopper la lecture et sera en outre indispensable à la réalisation d’une application graphique utilisable. Question 1 Créer une classe visuelle GUI qui permettra de lancer la lecture d’un fichier sonore et de sélectionner les effets sonores qui lui sont appliqués. Les fonctionnalités minimales exigées sont illustrées par l’exemple de la figure 3. Pour la manipulation de la classe GUI, il faudra que la fonction main comporte les lignes suivantes : JFrame gui = new GUI() ; gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ; 5 Fig. 4 – Interface et fonctionnalités normales gui.setVisible(true) ; Toutes les autres opérations (application d’un effet ou non) se fera dans les méthodes de la classe GUI. Question 3 S’il n’y a pas d’effet ou de filtrage quelconques, revenir aux séances précédentes pour compléter le modèle. Question 4 Une réalisation raisonable devrait ressembler à la figure 4. Question 5 Il s’agit désormais de réaliser un traceur de "fonctions" pour visualiser le spectre de vos signaux. Créer 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) ; 6 qui 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 fen frame.getContentPane().add(fig) ; frame.setSize(400, 300) ; // taille de la fenetre, que vous pouvez redimensionner frame.setVisible(true) ; Écrire une fonction TracerGraphe prenant en argument un tableau de double et traçant le graphique correspondant. Tester. 7