Magistère de Physique Fondamentale d`Orsay Rapport de M1

Transcription

Magistère de Physique Fondamentale d`Orsay Rapport de M1
Magistère de Physique Fondamentale d'Orsay
Rapport de M1 - Pro jet de programmation en
C++
Année 2008-2009
Fabien NUGIER1
Synthèse Sonore (Pro jet
1 [email protected]
1
N ◦1)
Table des matières
1 Introduction.
1
2 Comment jouer avec ce programme et s'en faire un premier avis...
1
3 L'arborescence du programme et les dépendances des objets.
1
3.1
Le Main(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
3.2
L'implémentation de l'Oscillateur.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
3.3
L'implémentation de la classe Note . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
3.4
Implémentation de la classe ADSR. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
4 Création de ltres pour modier le signal.
11
4.1
Quelque dénitions très générales.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
4.2
Implémentation de la classe Filtre.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
4.3
Quelque résultats sur les ltres. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
5 Les délais et autres eets dans le son.
16
5.1
La ligne à retard. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
5.2
Les eets.
17
5.3
Quelque résultats sur les eets.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19
6 La fonction portAudioCallback2 : le centre névralgique du programme.
19
7 Les autres classes et fonctions utilisées pour l'achage du signal et l'interface clavier.
23
7.1
7.2
L'achage par SDL du signal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
7.1.1
Le pixel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
7.1.2
Le signal, un ensemble de pixels.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
L'interface clavier SDL.
8 Conclusion.
29
1 Introduction.
Ce pro jet de C++ vise à créer un synthétiseur de son assez modeste gérant quelque-uns des aspects de base
de la synthèse sonore. Le programme comporte diérents chiers qui ont le plus souvent une fonction qui leur
est propre. On distingue en particulier l'oscillateur qui permet la création de l'onde (diérents types d'ondes
sont possibles), la note qui en dénit les paramètres sonores, l'enveloppe ADSR qui permet de moduler dans le
temps le volume de la note, les ltres qui permettent le ltrage du son, et enn les eets qui apportent un peu
plus de richesse aux sons disponibles. On cherchera dans ce document à expliquer le role de ces diérents objets
dans le projet ainsi que l'implémentation des diérentes parties du programme.
2 Comment jouer avec ce programme et s'en faire un premier avis...
On présente dans cette section les diérentes touches qui sont utilisées par le programme de synthèse sonore.
26 touches sont utilisées :
a, b, c, d, e, f, g sont les notes d'une gamme. Une seule gamme est présente.
h est une note de fréquence variable.
l : touche de modulation du facteur de qualité du ltre. Donne des résultats lorsque le mode ltre est
activé, rejoue la note précédente dans le cas contraire.
m : touche de modulation de la fréquence de coupure du ltre. Donne des résultats lorsque le mode ltre
est activé, rejoue la note précédente dans le cas contraire.
p : passage canal gauche - deux canaux (droit et gauche).
x : touche d'activation ou désactivation du mode Filtrage du son.
w : touche d'activation ou désactivation du mode Achage du son. Permet aussi de prendre en photo le
signal si activé pendant le délement.
F1, F2, F3, F4, F5 : 5 ltres diérents : LowPassFilter, HighPassFilter, BandPassFilter, Notch, AllPassFilter.
F6, F7, F8 : 3 eets diérents : SimpleDelay, FeedBackDelay, PassAllFilter.
F9, F10, F11, F12 : 4 sortes de types d'oscillateurs : sinusoide, triangle, carre, bruit.
q : quitter le programme.
3 L'arborescence du programme et les dépendances des objets.
Le chier principal est évidement
Main.cpp,
c'est le chier qui gère l'appel de la callback, fonction utilisée
pour créer les sons, il gère aussi l'initialisation et la réception des informations renvoyées par la bibliothèque
SDL et il contient les déclarations des objets utilisés. Le chier
Fonctions.cpp
contient la callback ainsi qu'une
fonction auxiliaire. Cette dernière n'étant pas utilisée, on ne la décrira pas dans la suite. Les chiers
Oscilla-
teur.h, Oscillateur.cpp, Note.h, Note.cpp, ADSR.h ,ADSR.cpp,Filtre.h, Filtre.cpp, Signal.h, Signal.cpp, Eet.h,
Eet.cpp, LigneRetard.h, LigneRetard.cpp
sont les classes que j'ai eu à créer et qui vont être décrites dans la
suite.
3.1 Le Main().
1
5
10
15
20
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
#include <fstream>
//
using namespace std;
#endif
pour stocker dans un fichier
#include "fftw3.h"
#include "portaudio.h"
#include "SDL_Pixel_Mag.h"
#define
#define
#define
#define
#define
PORTAUDIO_CONFIG_PAR_DEFAUT
SAMPLE_RATE 44100 //44100
BUFFER_SIZE 512 //512
TAILLE_FEN_X 1200
TAILLE_FEN_Y 700
using namespace std;
1
25
30
35
40
45
50
55
#include
#include
#include
#include
#include
#include
#include
#include
"Oscillateur.h"
"Note.h"
"ADSR.h"
"Filtre.h"
"pixel.h"
"Signal.h"
"LigneRetard.h"
"Effet.h"
Oscillateur Oscillo(44100,sinusoide);
Note Nota(440.0);
ADSR Enveloppa (100000,200000,800000,1200000,1.0,0.5);
Filtre Filtra ;
Signal Signalo ;
LigneRetard LignaRetardo(441000);
Effet Effecto ;
#include "Fonctions.cpp"
int main(int argc, char* args[])
{
PaStream *stream;
PaStreamParameters outputParameters;
PaError err;
SDL_Surface *screen;
// Initialisation SDL pour appel clavier et graphe
if( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) return false;
CreerFenetre(TAILLE_FEN_X,TAILLE_FEN_Y);
// Initialisation de PortAudio (Pa)
err = Pa_Initialize();
#ifdef PORTAUDIO_CONFIG_PAR_DEFAUT //--------------------------------------------------------------------------------------------Oscillo.RemplirTableOnde();
60
65
70
75
80
85
90
95
err = Pa_OpenDefaultStream( &stream, 0, 2, paFloat32, SAMPLE_RATE, BUFFER_SIZE, portAudioCallback2, NULL );
//-------------------------------------------------------------------------------------------------------------------------------#else
int NumeroMaterielAcLatenceMinimum = InfoMatos();
const PaDeviceInfo* PtInfoMatos;
PtInfoMatos = Pa_GetDeviceInfo(NumeroMaterielAcLatenceMinimum);
outputParameters.device = NumeroMaterielAcLatenceMinimum;
outputParameters.channelCount = 2;
outputParameters.sampleFormat = paFloat32;
outputParameters.suggestedLatency = PtInfoMatos->defaultHighOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream( &stream, NULL, &outputParameters , SAMPLE_RATE, BUFFER_SIZE, paFloat32, portAudioCallback2, NULL);
// err = Pa_OpenStream( &stream, NULL, &outputParameters, SAMPLE_RATE, BUFFER_SIZE, paFloat32, portAudioCallback, NULL);
#endif
//-------------------------------------------------------------------------------------------------------------------------------err = Pa_StartStream( stream );
// Boucle SDL des evenement, elle est appelee tant que l'utilisateur ne clique pas sur croix
bool Quit = false;
while(Quit == false)
{ SDL_event(&Quit, Oscillo, Nota, Filtra, Signalo, LignaRetardo, Effecto);
Nota.Actualise_temps();
}
// Au dela de la boucle while on est dans la phase de sortie du programme
// On quitte SDL
QuitSDL();
// On quitte PortAudio
err = Pa_StopStream( stream );
err = Pa_CloseStream( stream );
Pa_Terminate();
cout << err;
// Oscillo.AfficheTableOnde();
100
return 0;
}
On déclare tout d'abord les bibliothèques usuelles. Il faut noter qu'on appelle <SDL/SDL.h>. La bibliothèque SDL permet la gestion du clavier et de l'achage d'images. On déclare ensuite les objets que l'on utilise
dans le programme. Il est important que la directive
#include Fonctions.cpp
soit introduite après les déclara-
tions des objets, pour que la fonction portAudioCallBack2 puisse les connaître. Pour l'ob jet Oscillo, on précise
2
le nombre de points à utiliser pour générer la table d'onde (cf. suite pour une dénition), ainsi que le type
d'onde que l'on veut générer (sinusoide, carré..). La note Nota (ob jet de type Note) prend la valeur du La de
440Hz par défaut. On dénit aussi l'enveloppe, le ltre, le signal, la ligne à retard et l'eet qui seront utilisés
(et décrits) dans la suite.
On initialise tout d'abord un environnement SDL. Ensuite, la déclaration
quant à elle d'initialiser la bibliothèque PortAudio, lié au chier
err = Pa_Initialize()
portaudio.h
nous permet
que l'on ne décrira pas ici. Cette
bibliothèque gère l'émission du son par l'ordinateur.
La fonction
P a_OpenDef aultStreamelle
permet d'appeller la callback portAudioCallBack2 qui elle aura
une importance capitale dans la suite puisque c'est elle qui communique avec la carte son. Cette fonction est
appelée automatiquement tous les
512
et
44100).
BU F F ER_SIZE
SAM P LE _RAT E
secondes (i.e. tous les 11.6 ms avec les valeurs par défaut de
Il est très important d'avoir toujours cela en mémoire pour bien comprendre le fonctionnement
de la callback.
La partie
#else ... #endif
s'occupe de la gestion du matériel, en particulier la carte son du pc.
La deuxième partie la plus importante du main est la boucle while innie qui permet de communiquer
avec le clavier (la fonction
SDL_event(...)
fournissant les ob jets qui sont aectés par les touches du clavier),
d'actualiser le temps (attention, ce temps est en unité des sample, i.e. d'échantillon), et de quitter SDL lorsque
l'utilisateur en fait la demande. On quitte ensuite SDL et PortAudio.
3.2 L'implémentation de l'Oscillateur.
1
enum TypeOnde {aucune, sinusoide, carre, triangle, bruit} ;
class Note;
class Filtre;
5
class Oscillateur
{
private:
float * p_m_TableOnde;
float * p_m_TableOnde_debut;
int m_NbrPtTableOnde;
float m_indexTableOnde;
float m_out;
TypeOnde m_TypeOnde;
bool m_IsCanalDroitActive;
10
15
public:
Oscillateur (int m_NbrPtTableOnde_utilisateur = 44100,TypeOnde m_TypeOnde_utilisateur = aucune);
~Oscillateur ();
void RemplirTableOnde ();
void AfficheTableOnde ();
void ActualiseIndexTableOnde (float u_frequence);
void ActualiseSortieTableOnde ();
20
float get_m_out () { return m_out; }
void Change_TypeOnde (TypeOnde Nouveau_Type_Onde) { m_TypeOnde = Nouveau_Type_Onde ; RemplirTableOnde() ; }
void Change_IsCanalDroitActive ();
25
// Fonctions amies avec la classe Note
float get_Note (Note Note_u);
bool get_state (Note Note_u);
unsigned long get_temps (Note Note_u);
float get_intensite (Note Note_u);
char get_Touche(Note Note_u);
bool get_IsCanalDroitActive();
30
35
};
// Fonctions amies à la fois de la classe Filtre et de la classe Note
float get_Q_variable(Filtre Filtre_u, Note Note_u);
float get_FreqCoupure_variable(Filtre Filtre_u, Note Note_u);
On déclare tout d'abord une énumération qui nous permet de diérencier cinq tables d'ondes possibles,
et donc cinq types de signaux diérents :
aucune
(toujours nul),
sinusoide, carre, triangle,
et
bruit.
Il faut
aussi préciser qu'il existe une classe Note et une classe Filtre, car elles seront utilisées par des fonctions amies
de la classe Oscillateur. Les éléments et les fonctions membres de la classe vont être décrits juste après. On
remarquera l'utilisation du préxe m_ pour diérencier les données
1
\scriptsize
\begin{verbatim}
#ifndef HEADERS_DE_BASE
3
membres
des autres variables.
5
10
15
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
// pour stocker dans un fichier
#include <fstream>
using namespace std;
#endif
#define SAMPLE_RATE 44100 //44100
#define PI 3.141592569
20
25
30
35
40
45
50
55
60
65
70
75
80
85
90
#include "Oscillateur.h"
#include "Note.h"
#include "Filtre.h"
//----------------------------------------------------------------------------------------------------------------------------------Oscillateur::Oscillateur(int m_NbrPtTableOnde_utilisateur,TypeOnde m_TypeOnde_utilisateur)
{
m_TypeOnde = m_TypeOnde_utilisateur;
m_NbrPtTableOnde = m_NbrPtTableOnde_utilisateur;
p_m_TableOnde = new float[m_NbrPtTableOnde];
p_m_TableOnde_debut = p_m_TableOnde ;
m_indexTableOnde = 0;
m_out = 0;
m_IsCanalDroitActive = 1;
}
//----------------------------------------------------------------------------------------------------------------------------------Oscillateur::~Oscillateur () {delete [] p_m_TableOnde_debut;}
//----------------------------------------------------------------------------------------------------------------------------------void Oscillateur::RemplirTableOnde()
{
//
cout << "Type choisi lors de la construction: " << m_TypeOnde << endl;
//
cout << "Nombre de points choisi pour la table d'onde: " << m_NbrPtTableOnde << endl;
//
cout << "adresse ptr dans RemplirTableOnde() " << p_m_TableOnde << endl;
p_m_TableOnde = p_m_TableOnde_debut ;
switch(m_TypeOnde)
{
case aucune:
for(int i=0;i<m_NbrPtTableOnde;i++) {*p_m_TableOnde = 0;p_m_TableOnde++;}
break;
case sinusoide :
for(int i=0;i<m_NbrPtTableOnde;i++) {*p_m_TableOnde = sin(2*PI/m_NbrPtTableOnde*i);p_m_TableOnde++;}
break;
case carre :
for(int i=0;i<m_NbrPtTableOnde;i++)
{
if (i<m_NbrPtTableOnde/2) *p_m_TableOnde = 1;
if (i==m_NbrPtTableOnde/2) *p_m_TableOnde = 0;
if (i>m_NbrPtTableOnde/2) *p_m_TableOnde = -1;
p_m_TableOnde++;
}
break;
case triangle :
for(int i=0;i<m_NbrPtTableOnde;i++)
{
if (i<m_NbrPtTableOnde/4) {*p_m_TableOnde = (double)(i)/(m_NbrPtTableOnde/4);}
if ((i>m_NbrPtTableOnde/4)&&(i<m_NbrPtTableOnde*3/4)) {*p_m_TableOnde = -4./m_NbrPtTableOnde*((double)(i)) + 2. ;}
if (i>m_NbrPtTableOnde*3/4) {*p_m_TableOnde = (double)(i)*(4./m_NbrPtTableOnde) - 4.;}
p_m_TableOnde++;
}
break;
case bruit :
#if 1
srand ( time(NULL) );
for(int i=0;i<m_NbrPtTableOnde;i++)
{ *p_m_TableOnde = (rand()%1000 - 500)*0.002; p_m_TableOnde++ ; }
#endif
#if 0 // Une fonction compliquée qui a grosso modo le même effet.
for(int i=0;i<m_NbrPtTableOnde;i++) {*p_m_TableOnde = sin(tan(i)+17*cosh(i-47));p_m_TableOnde++;}
#endif
break;
}
}
//----------------------------------------------------------------------------------------------------------------------------------void Oscillateur::AfficheTableOnde() // Ma petite fonction membre perso
{
fstream TableOndeGraph("./TableOndeGraph.res",ios::out);
4
for(int i=0;i<m_NbrPtTableOnde;i++)
{ TableOndeGraph << 2*PI/m_NbrPtTableOnde*i << " " << *(p_m_TableOnde_debut + i) << endl;}
TableOndeGraph.close();
95
fstream TableOndeGraphGnu("./TableOndeGraph.gnu",ios::out);
TableOndeGraphGnu << "plot './TableOndeGraph.res' w p" << endl;
TableOndeGraphGnu << "pause -1" << endl;
TableOndeGraphGnu.close();
100
105
system("gnuplot './TableOndeGraph.gnu'");
}
//----------------------------------------------------------------------------------------------------------------------------------void Oscillateur::ActualiseIndexTableOnde(float u_frequence)
{
double Inc = u_frequence * m_NbrPtTableOnde / SAMPLE_RATE ;
110
if(Inc > m_NbrPtTableOnde)
cout << "Attention! Fréquence demandée supérieure à la fréquence d'échantillonnage de la table d'onde.
Le son n'aura pas la qualitée voulue!" << endl;
115
120
125
m_indexTableOnde = m_indexTableOnde + Inc;
if(m_indexTableOnde > m_NbrPtTableOnde) {m_indexTableOnde -= m_NbrPtTableOnde ;}
}
//----------------------------------------------------------------------------------------------------------------------------------void Oscillateur::ActualiseSortieTableOnde()
{
#if 0
// Interpolation à l'ordre 0 : on prend la partie entière de m_indexTableOnde.
int m_indexTableOnde_i = (int)(m_indexTableOnde);
m_out = *(p_m_TableOnde_debut + m_indexTableOnde_i);
//
cout << m_indexTableOnde << " " << m_out << endl;
#endif
#if 1
130
#endif
135
140
145
150
// Interpolation à l'ordre 2 non détectable à l'oreille par rapport à l'ordre 1.
}
//----------------------------------------------------------------------------------------------------------------------------------void Oscillateur::Change_IsCanalDroitActive()
{
if(m_IsCanalDroitActive==0) m_IsCanalDroitActive = 1;
else if(m_IsCanalDroitActive==1) m_IsCanalDroitActive = 0;
}
//----------------------------------------------------------------------------------------------------------------------------------// Fonction amies de la classe Note
//----------------------------------------------------------------------------------------------------------------------------------float Oscillateur::get_Note(Note Note_u)
{
#if 1
float freq_variable;
float FreqMin_u = 0.;
float FreqMax_u = 20000.;
155
160
165
170
175
// Interpolation à l'ordre 1 :
int m_indexTableOnde_i = (int)(m_indexTableOnde);
float m_out_i = *(p_m_TableOnde_debut + m_indexTableOnde_i);
float p = *(p_m_TableOnde_debut+ m_indexTableOnde_i + 1) - *(p_m_TableOnde_debut+ m_indexTableOnde_i) ;
m_out = m_out_i + p * (m_indexTableOnde - m_indexTableOnde_i);
#endif
#if 0
if(get_Touche(Note_u)=='h')
{
freq_variable = (float)(FreqMin_u + get_temps(Note_u)%(int)(FreqMax_u-FreqMin_u))*0.1;
return freq_variable;
}
else return Note_u.m_frequence ;
return Note_u.m_frequence ;
#endif
}
//----------------------------------------------------------------------------------------------------------------------------------bool Oscillateur::get_state(Note Note_u) { return Note_u.m_On ; }
//----------------------------------------------------------------------------------------------------------------------------------unsigned long Oscillateur::get_temps(Note Note_u) { return Note_u.m_temps ; }
//----------------------------------------------------------------------------------------------------------------------------------float Oscillateur::get_intensite(Note Note_u) { return Note_u.m_intensite ; }
//----------------------------------------------------------------------------------------------------------------------------------char Oscillateur::get_Touche(Note Note_u) { return Note_u.m_ToucheClavierAssociee ; }
//----------------------------------------------------------------------------------------------------------------------------------bool Oscillateur::get_IsCanalDroitActive() { return m_IsCanalDroitActive ; }
//-----------------------------------------------------------------------------------------------------------------------------------
// Fonctions amies de la classe Filtre
5
180
185
//----------------------------------------------------------------------------------------------------------------------------------float Oscillateur::get_Q_variable(Filtre Filtre_u, Note Note_u)
{
float Q_variable = Filtre_u.get_Q();
float Q_Min_u = 0.;
float Q_Max_u = 10000.;
if(get_Touche(Note_u)=='m')
{
// On prend la fréquence de la note précédente
Note_u.set_frequence(Note_u.get_TouchePrecedente());
190
}
else
{
195
200
205
}
return Filtre_u.get_Q();
}
//----------------------------------------------------------------------------------------------------------------------------------float Oscillateur::get_FreqCoupure_variable(Filtre Filtre_u, Note Note_u)
{
float FreqCoupure_variable = Filtre_u.get_FreqCoupure();
float FreqCoupureMin_u = 0. ;
float FreqCoupureMax_u = 5000. ;
if(get_Touche(Note_u)=='l')
{
// On prend la fréquence de la note précédente
Note_u.set_frequence(Note_u.get_TouchePrecedente());
210
215
Q_variable = (float)(10000.*Q_Min_u + get_temps(Note_u)%(int)(10000.*(Q_Max_u-Q_Min_u))) * (1./10000.);
return Q_variable;
}
else
{
}
FreqCoupure_variable = (float)(FreqCoupureMin_u + get_temps(Note_u)%(int)(FreqCoupureMax_u-FreqCoupureMin_u)) ;
return FreqCoupure_variable ;
return Filtre_u.get_FreqCoupure();
}
//----------------------------------------------------------------------------------------------------------------------------------\end{verbatim}
\normalsize
Décrivons tout d'abord le constructeur d'Oscillateur et les éléments de la classe. Il y a tout d'abord un type
d'onde
m_T ypeOnde
qui contient le type d'Oscillateur que l'on utilise. La fonction new nous permet d'ini-
tialiser un tableau-pointeur (allocation dynamique) avec le pointeur
p_m_T ableOnde_debut,
et de
m_N brP tT ableOnde
p_m_T ableOnde2 ,
de première adresse
éléments. Ce tableau nous sert à dénir la table d'onde.
Précisons tout d'abord qu'en informatique, il n'existe pas de signal continu et que toute information doit
être discrétisée, ou échantillonnée. Une table d'onde est simplement l 'image échantillonnée à une certaine
SAM P LE _RAT E )
fréquence (
du signal qu'on veut tracer. Il sut ainsi de lire la table d'onde plutôt que
de recalculer des valeurs (comme le faisait portAudioCallBack) inutilement. Le premier avantage d'une table
d'onde est donc un gain en temps de calcul. L'autre gros avantage de la table d'onde est qu'il sut de la lire à
un vitesse diérente pour avoir une fréquence diérent, soit encore un gain en temps de calcul.
Les autres données membres sont aussi aussi
m_indexT ableOnde l'index de la table, m_out la valeur
m_IsCanalDroitActive qui vaut 1 si on a activé
retournée par défaut pour l'amplitude du son (cf. suite) et
les deux canaux sonore de l'ordinateur (dans le cas à 2 canaux).
Décrivons maintenant les fonctions membres. La fonction
RemplirT ableOnde()
est assez simple et un peu
d'analyse nous amène très rapidement aux expression contenues dans le switch. Notons tout de même l'utilisation
de deux manières diérentes de génération du bruit. La première est une méthode simple pour générer un bruit
entre -1 et 1 avec une fonction random (et 3 chires signicatifs pour les valeurs). La deuxième est tout
3
simplement le choix heuristique d'une fonction assez chaotique qui donne une bonne représentation du bruit .
La fonction
Af f icheT ableOnde()
est une fonction qui utilise le logitiel Gnuplot pour acher la table d'onde.
Elle peut être utilisée dans le Main en tant que contrôle, mais ne présente pas un grand intéret.
ActualiseIndexT ableOnde(f loat u_f requence), quant à elle, est beaucoup plus importante car
Inc est un réel
informatique) et qu'il en est de même pour m_indexT ableOnde. On s'assure en n de fonction que
La fonction
c'est elle qui gère la vitesse de parcours de la table d'onde. Il faut noter que la variable locale
(au sens
ce dernier ne dépasse pas la valeur du nombre de points de la table d'onde. En eet, comme on peut le voir
sur la gure 1, la discrétisation par l'échantillonnage donne toute les chances à
m_intexT ableOnde de tomber
ActualiseSortieT ableOnde()
entre deux valeurs de la table. Cette fonction est intimement liée à la fonction
2 On note p_ pour pointeur
3 Comme ce bruit ne nécessite
pas obligatoirement d'être blanc (i.e. qu'il contienne toutes les fréquences en même proportion),
nous n'avons pas de contraite particulière sur sa génération.
6
qui se charge de corriger le problème de la valeur réelle de
m_indexT ableOnde
qui est incompatible avec les
valeurs discrétisées de la table d'onde. On peut utiliser la première méthode qui est une interpolation des points
à l'ordre zéro, ce qui reviens à prendre, pour une donnée, la valeur partie entière du temps (en unité de samples
toujours) donné par
Inc,
cette partie entière est
m_indexT ableOnde_i.
La seconde méthode est de faire une
interpolation linéaire entre les deux points de la table d'onde qui entourent
m_indexT ableOnde.
A l'oreille, on
constate bien un son plus pure que pour l'interpolation à l'ordre 0. Cette diérence est néanmoins assez faible
4
pour ne pas faire une interpolation à l'ordre 2 qui nécessiterai, qui plus est, bien plus de calculs .
Fig. 1: Illustration de l'échantillonnage d'un signal et de l'interpolation des points aux ordres 0 et 1. La valeur
retenue lors de l'interpolation est entourée d'un cercle bleu.
Change_IsCanalDroitActive() permet d'activer ou désactiver le canal droit.
get_N ote(N ote N ote_u) permet de renvoyer la fréquence d'une note N ote_u5 .
Elle permet aussi, au pas-
sage, de générer une note de fréquence aléatoire lorsque l'on presse la touche 'h'. Bien que cette expression
soit un peu heuristique, il s'avère qu'elle donne des résultats aussi bons, voir meilleurs, que ce que donnerai
l'utilisation d'un autre oscillateur uniquement dédié à la variation de la fréquence de la note, chose que j'ai
essayé de faire et qui a causé de nombreux problèmes que je n'ai nalement pas pu résoudre.
Les cinq autres fonctions membres qui suivent sont assez explicites pour ne pas être décrites.
Les deux dernières fonction, amies de la classe Filtre, gèrent la modulation de la fréquence de coupure des
ltres ainsi que leur facteur de qualité. Ces fonctions sont codées sur le même principe que
get_N ote(N ote N ote_u),
i.e. avec une expression euristique. On s'arrange aussi pour partir de la fréquence de la dernière note jouée autre
que 'h', 'l', ou 'm', et ce pour que l'utilisation des touches soit cohérente.
3.3 L'implémentation de la classe Note
1
5
10
class Oscillateur;
class Note
{
private:
float m_frequence;
unsigned long m_temps;
float m_intensite;
char m_ToucheClavierPrecedente;
char m_ToucheClavierAssociee;
bool m_On;
public:
Note (float freq = 440);
4 Il
faudrait alors utiliser les polynomes de Lagrange d'ordre 2 (paraboles) et déterminer les trois coecients puis tracer la
parabole et trouver le point.
5 Remarque
: on met le suxe _u pour dire que c'est un élément donné par l'utilisateur (au sens de la POO).
7
15
20
25
30
35
40
~Note ();
friend float Oscillateur::get_Note (Note Note_u);
friend bool Oscillateur::get_state (Note Note_u);
friend unsigned long Oscillateur::get_temps (Note Note_u);
friend float Oscillateur::get_intensite (Note Note_u);
friend char Oscillateur::get_Touche(Note Note_u);
friend float Oscillateur::get_Q_variable(Filtre Filtre_u, Note Note_u);
friend float Oscillateur::get_FreqCoupure_variable(Filtre Filtre_u, Note Note_u);
//----------------------------------------------------------------------------------------------------------------------------------void set_state(bool Note_state_u)
{ m_On = Note_state_u ;
//
cout << "Etat de la note : " << m_On << endl;
//
cout << "Temps avant remise à zéro : " << m_temps << endl;
m_temps = 0;
}
//----------------------------------------------------------------------------------------------------------------------------------void set_frequence(char caractere_note_u);
void set_frequence(float frequence_note_u);
//----------------------------------------------------------------------------------------------------------------------------------void set_intensite(float m_intensite_u)
{ m_intensite = m_intensite_u ; //cout << "Intensité fixée à : " << m_intensite << endl;
}
//----------------------------------------------------------------------------------------------------------------------------------void Actualise_temps() { m_temps++;}
//----------------------------------------------------------------------------------------------------------------------------------char get_TouchePrecedente() { return m_ToucheClavierPrecedente; }
//----------------------------------------------------------------------------------------------------------------------------------};
m_f requence), d'une
m_temps), une variable prenant en compte l'intensité
Les membres de la classe Oscillateur sont constitués de la fréquence de l'oscillateur (
variable qui contient le temps écoulé en unités de samples (
(même si elle n'a pas été exploité ensuite), deux variables pour gérer les touches et une variable d'état.
Décrivons brièvement les fonctions déclarées inline dans la dénition de la classe Note.
set_state(bool N ote_state_u) est utilisée pour attribuer la valeur 0 ou la valeur 1 à N ote_state_u
SDL_event(...) dénie dans le chier SDL_clavier.cpp.Les autres fonctions, à savoir set_intensite(), Actualise_temps(), et
get_T oucheP recedente() servent à dénir l'intensité de la Note (de manière globale, quelque soient les eets) La fonction
selon que l'on veut une note éteinte ou active. Cette fonction sera utilsée par la fonction
cette fonction a été utilisée au début mais j'ai ensuite laissé toutes les intensités à 1 , à actualiser le temps de
la Note (qui sera utile surtout pour l'enveloppe ADSR), et à obtenir la touche pressée avant la touche courante
(utile pour les touches 'h', 'l', et 'm'). Ces fonctions sont déclarées inline car elles sont simples (pas de relation
d'amitié) et parce que leur code n'est pas trop long. Enn, les fonctions amies de la classe Oscillateur ont été
décrites dans la section 3.2.
1
5
10
15
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
// pour stocker dans un fichier
#include <fstream>
using namespace std;
#endif
#include "fftw3.h"
#include "portaudio.h"
#include "SDL_Pixel_Mag.h"
#define PORTAUDIO_CONFIG_PAR_DEFAUT
20
#define
#define
#define
#define
#define
25
#include "Oscillateur.h"
#include "Note.h"
30
35
SAMPLE_RATE 44100 //44100
BUFFER_SIZE 512 //512
TAILLE_FEN_X 800
TAILLE_FEN_Y 600
PI 3.141592569
// attention, ne sera choisie que la puissance de 2 >= à la valeur
//----------------------------------------------------------------------------------------------------------------------------------Note::Note(float m_frequence_u)
{
m_frequence = m_frequence_u;
m_On=0;
m_temps=0;
m_intensite=1;
m_ToucheClavierPrecedente='q';
m_ToucheClavierAssociee='q';
8
40
45
50
}
//----------------------------------------------------------------------------------------------------------------------------------Note::~Note() {}
//----------------------------------------------------------------------------------------------------------------------------------void Note::set_frequence(char caractere_note_u)
{
if((caractere_note_u == 'm')||(caractere_note_u == 'l')) m_ToucheClavierAssociee = caractere_note_u ;
else
{ m_ToucheClavierPrecedente = m_ToucheClavierAssociee ;
// On définit la nouvelle touche
m_ToucheClavierAssociee = caractere_note_u ;
}
//
cout << "La touche precedente etait : " << m_ToucheClavierPrecedente << endl;
//
cout << "La touche actuelle est : " << m_ToucheClavierAssociee << endl;
switch(m_ToucheClavierAssociee)
{
case 'a' : m_frequence = 440.0 ; break;
case 'b' : m_frequence = 493.9 ; break;
case 'c' : m_frequence = 523.0 ; break;
case 'd' : m_frequence = 587.0 ; break;
case 'e' : m_frequence = 659.0 ; break;
case 'f' : m_frequence = 698.5 ; break;
case 'g' : m_frequence = 784.0 ; break;
case 'h' : break;
case 'm' : /*cout << "note dédiée à la variation de Q du Filtre." << endl;*/ break;
case 'l' : /*cout << "note dédiée à la variation de la fréquence de coupure du Filtre." << endl;*/ break;
}
55
60
65
70
}
//----------------------------------------------------------------------------------------------------------------------------------void Note::set_frequence(float frequence_note_u)
{
m_frequence = frequence_note_u ;
}
//-----------------------------------------------------------------------------------------------------------------------------------
Le constructeur de la classe Note est assez simple pour ne pas être décrit, il en est de même pour la fonction
set_f requence(f loat f requence_note_u). Quant à la fonction set_f requence(char caractere_note_u), c'est
une surdénition de la précédente qui, lorsqu'elle reçoit une variable char de 'a' à 'g' en argument et attribue
la bonne fréquence de note en dans la Note qui l'appelle. Elle se charge aussi de prendre la fréquence de la
note précédente dans le cas des touches 'l' et 'm'. Ainsi on peut faire varier les paramètres du ltre de la note
précédement jouée.
3.4 Implémentation de la classe ADSR.
Commençons par rappeler le principe d'une enveloppe ADSR. Lorqu'un son est joué par la carte avec pour
seules données celles de la table d'onde et la fréquence de lecture, celle-ci lit les données avec une amplitude
maximale xée par ce que l'on a appelé Intensité de la Note
6
et que l'on a prise égale à 1. Il en résulte que
le son arrive de manière abrupte, et ce même si l'on démarre la table d'onde à 0 comme dans le cas d'un
signal sinusoidal ou d'un signal triangulaire. Ceci est tout simplement dû au fait que la fréquence de la note est
beaucoup plus grand que la fréquence de détection des variations sonores de l'oreille humaine, qui détecte une
moyenne. Il en est de même lorsque la note se coupe, elle passe directement à une intensité nulle.
Pour éviter cela, on choisi donc de moduler le son sur une grande échelle de temps comparée à sa période,
une échelle de temps adaptée à l'oreille humaine, soit de l'ordre de la seconde. Ainsi, on va pouvoir amener
progressivement le son, le garder à une certaine hauteur pendant un certain temps, puis le faire disparaître en
douceur. L'enveloppe que l'on utilise pour cela est l'une des plus simple qui soit, mais qui est assez utilisée dans
le domaine de l'acoustique, et qui se nomme ADSR. On a donc une phase d'attaque (Attack) : le son augmente
jusque
I = 1;
une phase de décente (Decay) jusque la valeur d'intensité à laquelle on veut maintenir la note un
certain temps ; une phase de subsistance (Sustain) : c'est habituellement la plus longue, la note a une intensité
constante ; et une phase de relachement (Release) : l'intensité tend vers 0.
1
5
class ADSR
{
private:
int m_T_Attack , m_T_Decay , m_T_Sustain , m_T_Release ;
float m_ValeurAttack , m_ValeurDecay ;
float m_Pente_Attack , m_Pente_Decay , m_Pente_Release ;
float m_OrdonneeOrigineDecay , m_OrdonneeOrigineRelease ;
public:
6 Il
s'agit en réalité de l'amplitude, mais l'oreille humaine n'est sensible qu'à son carré.
9
10
};
ADSR (int m_T_Attack_u=500000,int m_T_Decay_u=1000000,int m_T_Sustain_u=2000000,int m_T_Release_u=4000000
,float m_ValeurAttack_u=1.0,float m_ValeurDecay_u=0.5);
~ADSR ();
float EvalueADSR (unsigned long temps_note_samples_u);
On dénit dans la classe les coordonnées des points qui nous seront utiles pour dénir entièrement l'en-
m_T _Attack ,m_V aleurAttack ) ,
m_T _Release,0). Les autres
veloppe. On peut voir ces points sur la gure 2, avec pour corrdonnées M(
m_T _Decay ,m_V aleurDecay ),
N(
m_T _Sustain,m_V aleurDecay ),
P(
et Q(
données correspondent aux 3 pentes des phases A, D, et R, ainsi que les deux ordonnées à l'origine des droites
prolongées des phases D et R.
1
5
10
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
#include <fstream>
using namespace std;
#endif
#include "ADSR.h"
15
20
25
30
35
40
45
50
55
60
65
70
//----------------------------------------------------------------------------------------------------------------------------------ADSR::ADSR (int m_T_Attack_u,int m_T_Decay_u,int m_T_Sustain_u,int m_T_Release_u,float m_ValeurAttack_u,float m_ValeurDecay_u)
{
m_T_Attack = m_T_Attack_u;
m_T_Decay = m_T_Decay_u;
m_T_Sustain = m_T_Sustain_u;
m_T_Release = m_T_Release_u;
m_ValeurAttack = m_ValeurAttack_u;
m_ValeurDecay = m_ValeurDecay_u;
if((m_ValeurAttack<0.)||(m_ValeurAttack>1.)||(m_ValeurDecay<0.)||(m_ValeurDecay>1.))
cout << "Il y a un probleme au niveau des valeurs de m_ValeurAttack ou de m_ValeurDecay !" << endl;
// valeurs de m_T_Attack, m_T_Decay, m_T_Sustain, et m_T_Release en unités de Samples
m_Pente_Attack = m_ValeurAttack / m_T_Attack ;
m_Pente_Decay
= (m_ValeurDecay - m_ValeurAttack) / ((double)m_T_Decay - (double)m_T_Attack) ;
m_Pente_Release = (-1)*m_ValeurDecay / ((double)m_T_Release - (double)m_T_Sustain) ;
m_OrdonneeOrigineRelease = m_ValeurDecay - m_Pente_Release * (double)m_T_Sustain ;
m_OrdonneeOrigineDecay = m_ValeurAttack - m_Pente_Decay * (double)m_T_Attack ;
// cout << "*****" << endl;
// cout << m_T_Attack << " " << m_T_Decay << " " << m_T_Sustain << " " << m_T_Release << endl;
// cout << m_ValeurAttack << " " << m_ValeurDecay << endl;
// cout << "Pente_Attack " << m_Pente_Attack << " Pente_Decay " << m_Pente_Decay << " Pente_Release " << m_Pente_Release << endl;
// cout << "OrdonneeOrigineRelease " << m_OrdonneeOrigineRelease << " OrdonneeOrigineDecay " << m_OrdonneeOrigineDecay << endl;
// cout << "*****" << endl;
}
//----------------------------------------------------------------------------------------------------------------------------------ADSR::~ADSR () {}
//----------------------------------------------------------------------------------------------------------------------------------// Méthode qui donne l'amplitude de l'enveloppe pour un temps donné en sample
float ADSR::EvalueADSR (unsigned long temps_samples_u)
{
//
cout << endl;
//
cout << m_T_Attack << " " << m_T_Decay << " " << m_T_Sustain << " " << m_T_Release << endl;
//
cout << m_ValeurAttack << " " << m_ValeurDecay << endl;
float Amplitude_Enveloppe;
if(temps_samples_u < 0) cout << "Erreur, temps_samples_u ne peut pas etre negatif !" << endl;
if((temps_samples_u>0)&&(temps_samples_u<=m_T_Attack))
{
Amplitude_Enveloppe = m_Pente_Attack*temps_samples_u ;
}
if((temps_samples_u>m_T_Attack)&&(temps_samples_u<=m_T_Decay))
{
Amplitude_Enveloppe = m_OrdonneeOrigineDecay + m_Pente_Decay*temps_samples_u ;
}
if((temps_samples_u>m_T_Decay)&&(temps_samples_u<=m_T_Sustain))
{
Amplitude_Enveloppe = m_ValeurDecay;
}
if((temps_samples_u>m_T_Sustain)&&(temps_samples_u<=m_T_Release))
{
Amplitude_Enveloppe = m_OrdonneeOrigineRelease + m_Pente_Release*temps_samples_u ;
}
if(temps_samples_u>m_T_Release)
{
Amplitude_Enveloppe = 0. ;
10
75
//cout << "Attention, on demande un temps au dela de la taille de l'enveloppe !" << endl;
}
return Amplitude_Enveloppe;
}
//-----------------------------------------------------------------------------------------------------------------------------------
Le constructeur de la classe ADSR permet donc de calculer les valeurs de pentes et des ordonnées à l'origine
m_T _Attack _u, m_T _Decay _u, m_T _Sustain_u, m_T _Release_u,
m_V aleurAttack _u, m_V aleurDecay _u donnés pas l'utilisateur. La fonction EvalueADSR(...) permet quant
à elle de renvoyer la valeur de l'enveloppe pour un temps en samples donné (temps_samples_u)), et c'est cette
des phases à partir des arguments
fonction qui nous permettra donc de moduler l'intensité du son par l'enveloppe.
Fig. 2:
Distribution d'intensité d'une enveloppe ADSR.
4 Création de ltres pour modier le signal.
4.1 Quelque dénitions très générales.
On considérera ici des signaux périodiques ou pseudo-périodiques de pulsation
w.
Ci-dessous sont présentés
quelque dénitions simples relatives aux ltres.
Quadripôle : C'est un dispositif (électrique) comprenant 4 pôles, généralement 2 d'entrée et 2 de sortie. On
ue (t) = Ue cos(wt) le signal (électrique, audio, ...) entre
us (t) = Us cos(wt) le signal entre les 2 bornes de sortie. Ue
(complexes) ue (t) et us (t).
note
les 2 bornes d'entrée du quadripôle, on note aussi
et
Us
sont les amplitudes complexes des signaux
Fonction de transfert : Elle est dénie comme le rapport des amplitudes
Ue
et
Us ,
ce rapport dépendant de
la pulsation du signal :
H(jw) =
Us
Ue
On dénit alors pour le quadripôle un gain et une phase
7
(1)
:
G(w) =| H(jw) | et φ(w) = arg{H(jw)}
(2)
Filtre : Un ltre est un quadripôle de fonction de transfert particulière.
Octave : C'est un intervalle de fréquence compris entre une fréquence et le double de cette fréquence.
7 Ce
sont respectivement les caractéristiques de la transformation de l'amplitude (et de la phase) à multiplier (et additionner)
au signal d'entrée pour avoir le signal de sortie.
11
4.2 Implémentation de la classe Filtre.
1
enum TypeFiltre {LowPassFilter, HighPassFilter, BandPassFilter, Notch, AllPassFilter} ;
class Oscillateur;
class Note;
5
class Filtre
{
private:
TypeFiltre m_TypeFiltre;
int m_indice_LR;
float * m_ptr_LR_in;
float * m_ptr_LR_out;
float * m_ptr_LR_in_debut;
float * m_ptr_LR_out_debut;
bool m_On;
float m_freq_coupure;
float m_freq_coupure_default;
float m_Q;
float m_Q_default;
10
15
20
25
// Fréquence de coupure du Filtre
// Facteur de Qualité du Filtre
public:
Filtre();
~Filtre();
float calcul_out(float frequence_coupure_u, float facteur_qualite_u, float
void switch_state ();
void Change_TypeFiltreAFreqCoupCste (TypeFiltre TypeFiltre_u);
m_in_value_u);
friend float Oscillateur::get_Q_variable(Filtre Filtre_u, Note Note_u);
friend float Oscillateur::get_FreqCoupure_variable(Filtre Filtre_u, Note Note_u);
30
35
40
//----------------------------------------------------------------------------------------------------------------------------------bool get_filtre_state () { return m_On; }
//----------------------------------------------------------------------------------------------------------------------------------float get_Q () { return m_Q; }
//----------------------------------------------------------------------------------------------------------------------------------float get_FreqCoupure () { return m_freq_coupure; }
//----------------------------------------------------------------------------------------------------------------------------------void RestaureQParDefaut() { m_Q = m_Q_default; }
//----------------------------------------------------------------------------------------------------------------------------------void RestaureFreqCoupureParDefaut() { m_freq_coupure = m_freq_coupure_default; }
//----------------------------------------------------------------------------------------------------------------------------------};
On dénit cinq types de ltres qui sont : LowPassFilter (Passe-Bas), HighPassFilter (Passe-Haut), BandPassFilter (Passe-Bande), Notch (Coupe-Bande), AllPassFilter (Passe-Tout). Comme on va le voir, le ltre a
besoin de deux lignes à retard pour fonctionner. On vera dans la suite une classe LigneRetard qui génère une
ligne à retard. Elle ne sera pas utilisée dans la classe ltre car le ltre n'a besoin que d'une petite ligne à
retard, ce qui la rend inutile ici, de plus il en faut deux, et son utilisation imposerait l'utilisation d'un grand
nombre de méthodes du fait de l'encapsulation des données. Il est donc bien plus simple d'utiliser deux lignes
à retard internes à la classe Filtre. Ces lignes sont gérées par les quatre pointeurs de oat :
m_ptr_LR_out, m_ptr_LR_in_debut, m_ptr_LR_out_debut. m_On
m_ptr_LR_in,
décrit l'état (actif ou inactif ) du
ltre. Les autres données membres sont les fréquence de coupure et facteur de qualité du ltre.
Les fonctions
switch_state(),
calcul_out(f loat f requence_coupure_u, f loat f acteur_qualite_u, f loat m_in_value_u),
Change_T ypeF iltreAF reqCoupCste(T ypeF iltre T ypeF iltre_u) seront décrites dans la
et
suite. Quant aux sept autres fonctions, deux sont des fonctions amies de la classe Oscillateur et ont déjà été
décrites, les cinq autres sont on ne peut plus simples et il est inutile de les décrire.
1
5
10
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
#include <fstream>
//
using namespace std;
#endif
pour stocker dans un fichier
#define SAMPLE_RATE 44100 //44100
#define PI 3.141592569
15
#include "Oscillateur.h"
#include "Note.h"
#include "Filtre.h"
12
20
25
#define Nbr_samples_LR 8 // pour se donner de la marge
//----------------------------------------------------------------------------------------------------------------------------------Filtre::Filtre ()
{
m_TypeFiltre = LowPassFilter ;
m_freq_coupure = 500.; // Hz - fréquence de coupure souhaitée
m_freq_coupure_default = m_freq_coupure;
m_indice_LR = 0 ;
30
35
40
45
50
m_ptr_LR_in = new float [Nbr_samples_LR]; // Pointeur sur la ligne à retard des entrées du filtre.
m_ptr_LR_out = new float [Nbr_samples_LR];
// Pointeur sur la ligne à retard des sorties du filtre.
for(int i=0;i<Nbr_samples_LR;i++) { m_ptr_LR_in[i] = 0. ; m_ptr_LR_out[i] = 0. ; }
m_ptr_LR_in_debut = m_ptr_LR_in ;
m_ptr_LR_out_debut = m_ptr_LR_out ;
m_On = 0 ;
m_Q = 0.5 ; // c'est le facteur de qualité du filtre
m_Q_default = m_Q;
}
//----------------------------------------------------------------------------------------------------------------------------------Filtre::~Filtre () {}
//----------------------------------------------------------------------------------------------------------------------------------float Filtre::calcul_out(float frequence_coupure_u, float facteur_qualite_u, float m_in_value_u)
{
m_freq_coupure = frequence_coupure_u ;
m_Q = facteur_qualite_u ;
m_ptr_LR_in[m_indice_LR] = m_in_value_u ;
#if 0
55
60
65
70
75
80
85
90
95
100
105
#endif
// D'après le document Filtre.pdf
double alpha = 1. / (1. + double(SAMPLE_RATE)/freq_coupure);
// cout << "alpha = " << alpha << endl;
float m_a = alpha ;
float m_b = 1. - alpha ;
int j;
float y_n = 0. ;
for(int i=0;i<3;i++) //Nbr_samples_LR
{
if(m_indice_LR-i<0)
{
j = i - Nbr_samples_LR ;
y_n += m_a/3. * m_ptr_LR_in[m_indice_LR - j] ;
}
else
{
j = i ;
y_n += m_a/3. * m_ptr_LR_in[m_indice_LR - j] ;
}
}
for(int i=1;i<3;i++)
{
if(m_indice_LR-i<0)
{
j = i - Nbr_samples_LR ;
y_n += m_b/2. * m_ptr_LR_out[m_indice_LR - j] ;
}
else
{
j = i ;
y_n += m_b/2. * m_ptr_LR_out[m_indice_LR - j] ;
}
}
#if 1 // D'après le document Audio-EQ-Cookbook.txt
float y_n = 0.;
// m_indice_LR est une constante dans cette fonction
float w0 = 2*PI*m_freq_coupure/SAMPLE_RATE ;
float cosw0 = cos(w0) ;
float sinw0 = sin(w0) ;
float alpha = sinw0/(2*m_Q); // Dans le cas EE Q ------------------------> ??
float a[3]; float b[3];
switch (m_TypeFiltre)
{
case LowPassFilter :
b[0] = (1 - cosw0)/2 ;
b[1] =
1 - cosw0 ;
b[2] = (1 - cosw0)/2 ;
a[0] =
1 + alpha ;
a[1] = -2*cosw0 ;
a[2] =
1 - alpha ;
break;
case HighPassFilter :
b[0] = (1 + cosw0)/2 ;
b[1] = -(1 + cosw0) ;
13
110
break;
120
case Notch :
b[0]
b[1]
b[2]
a[0]
a[1]
a[2]
break;
125
130
}
140
145
150
155
170
175
180
=
1 ;
= -2*cosw0 ;
=
1 ;
=
1 + alpha ;
= -2*cosw0 ;
=
1 - alpha ;
case AllPassFilter:
b[0] =
1 - alpha
b[1] = -2*cosw0 ;
b[2] =
1 + alpha
a[0] =
1 + alpha
a[1] = -2*cosw0 ;
a[2] =
1 - alpha
break;
135
165
= (1 + cosw0)/2 ;
=
1 + alpha ;
= -2*cosw0 ;
=
1 - alpha ;
case BandPassFilter : // Le premier des 2 qui nous sont proposés dans le petit handbook des filtres
b[0] =
sinw0/2 ; // =
Q*alpha
b[1] =
0 ;
b[2] = -sinw0/2 ; // = -Q*alpha
a[0] =
1 + alpha ;
a[1] = -2*cosw0 ;
a[2] =
1 - alpha ;
break;
115
160
b[2]
a[0]
a[1]
a[2]
#endif
;
;
;
;
int j;
for(int i=0;i<3;i++) //Nbr_samples_LR
{
if(m_indice_LR-i<0)
{
j = i - Nbr_samples_LR ;
y_n += (b[i]/a[0]) * m_ptr_LR_in[m_indice_LR - j] ;}
else
{
j = i ;
y_n += (b[i]/a[0]) * m_ptr_LR_in[m_indice_LR - j] ;}
}
for(int i=1;i<3;i++)
{
if(m_indice_LR-i<0)
{
j = i - Nbr_samples_LR ;
y_n -= (a[i]/a[0]) * m_ptr_LR_out[m_indice_LR - j] ;}
else
{
j = i ;
y_n -= (a[i]/a[0]) * m_ptr_LR_out[m_indice_LR - j] ;}
}
m_ptr_LR_out[m_indice_LR] = y_n ;
// Actualise_2_lignes_retard
if (m_indice_LR < Nbr_samples_LR - 1) {m_indice_LR ++ ;}
else if (m_indice_LR = Nbr_samples_LR - 1) {m_indice_LR = 0 ;}
return y_n;
}
//----------------------------------------------------------------------------------------------------------------------------------void Filtre::switch_state()
{
if(m_On==0) m_On = 1 ;
else if(m_On==1) m_On = 0 ;
cout << "Etat du filtre : " << m_On << endl;
// cout << "Temps avant remise à zéro : " << m_temps << endl; m_temps = 0;
}
//----------------------------------------------------------------------------------------------------------------------------------void Filtre::Change_TypeFiltreAFreqCoupCste(TypeFiltre TypeFiltre_u)
{
m_TypeFiltre = TypeFiltre_u ;
}
//-----------------------------------------------------------------------------------------------------------------------------------
Le constructeur de Filtre dénit les valeurs par défaut des données membres, il déclare aussi les deux tableaux
dynamiques générés par les quatre pointeurs présentés au dessus.
La fonction
calcul_out(f loat f requence_coupure_u, f loat f acteur_qualite_u, f loat m_in_value_u) est
de loin la plus importante des fonctions de la classe Filtre puisqu'elle interviendra dans la portAudioCallBack2
pour le ltrage du signal sonore. Cette fonction reçoit en argument une valeur pour la fréquence de coupure
14
du ltre, et une valeur pour son facteur de qualité. Elle reçoit aussi une valeur du signal qu'elle stocke dans la
ligne à retard In, c'est la ligne à retard du son d'entrée, i.e. sans ltrage. La première partie de la fonction
#if 0...#endif et constitue la manière d'implémenter le ltre passe-bas selon la méthode
F iltre.pdf . La deuxième partie est celle qui est utilisée et qui se base sur les
document Audio − EQ − Cookbook.txt. Comme on peut le voir dans le switch, cette méthode
est commentée par
présentée dans le document
informations du
est plus automatiques car il sut de calculer les coecients des ltres avec les expressions du document.
Donnons tout de même le principe du calcul. Nos deux tables d'onde (contenant
N br_samples_LR = 8
oats) contiennent les derniers éléments du son, non-ltrés (In) et ltrés (Out). On connaît aussi la dernière
m_in_value_u) que
y[n], s'exprime en fonction de la valeur de x[n]
fonction calcul _out(...) :
x[n].
valeur du son en entrée (celle qui est arrivée en argument par
l'on note simplement
Alors, la valeur du son ltré, notée
et des valeurs enregistrées
lors des 2 derniers appels de la
b0
y[n] = x[n] +
a0
b1
a1
b2
a2
x[n − 1] + x[n − 2] −
y[n − 1] + y[n − 2]
a0
a0
a0
a0
(3)
La fonction aecte donc les valeurs aux coecients suivant le type de ltre choisi, puis elle eectue le calcul
y[n] avec deux boucles for et des conditions pour éviter le dépassement des tables d'onde. Enn, elle actualise
l'indice les lignes à retard et renvoie la valeur de y[n].
La fonction switch_state() passe le ltre en mode actif s'il est inactif, et en mode inactif s'il est actif.
Enn la fonction Change_T ypeF iltreAF reqCoupCste(T ypeF iltre T ypeF iltre_u) permet de changer le type
de
de ltre.
4.3 Quelque résultats sur les ltres.
Pour nir avec la question des ltres, nous présentons ici les signaux obtenus pour les cinq ltres étudiés
dans le cas d'un signal d'entrée carré. Ces résultats sont visibles en gure 3.
(a) LowPassFilter
(b) HighPassFilter
(c) BandPassFilter
(d) Notch
(e) AllPassFilter
Fig. 3: Signaux sonores après application de nos cinq ltres à un signal carré.
15
5 Les délais et autres eets dans le son.
On va expliquer dans cette partie l'implémentation des trois types d'eets qui ont été utilisés. Pour implémenter ces eets, on aura besoin de la classe LigneRetard qui va dénir la ligne à retard utilisée par les
eets.
5.1 La ligne à retard.
La ligne a retard est constituée d'un buer de taille xe que l'on remplit au fur et à mesure que le son est
joué. Cette ligne à retard est particulièrement utile dans le cas du ltrage ou de l'élaboration d'eets sonores.
On présente tout d'abord le code source de notre classe LigneRetard.
1
5
class LigneRetard
{
private:
int m_indice_LREffet;
float * m_ptr_LREffet_donnee;
float * m_ptr_LREffet_courant;
float * m_ptr_LREffet_fin;
int m_taille;
10
15
};
public:
LigneRetard (int m_taille_u);
~LigneRetard ();
void Insere (float valeur_u);
void Propage ();
float RetourneValeurCourante ();
float RetourneValeurPasse (int d_u, float alpha_u);
void NettoieLigneRetard();
La ligne à retard est donc l'ensemble d'un buer circulaire, et des méthodes pour le remplir et en extraire
m_indice_LREf f et l'indice courant sur le buer circulaire, m_ptr_LREf f et_donnee
m_ptr_LREf f et_f in un pointeur sur le dernier élément,
et m_ptr _LREf f et_courant un pointeur parcourant tout les éléments du buer circulaire compris entre ces
deux derniers pointeurs. Quand à m_taille, sa valeur dénit la taille du buer.
des valeurs. On dénit
est un pointeur sur le premier élément du buer,
1
5
10
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
#include <fstream>
using namespace std;
#endif
#include "LigneRetard.h"
15
20
25
30
35
//----------------------------------------------------------------------------------------------------------------------------------LigneRetard::LigneRetard (int m_taille_u)
{
m_taille = m_taille_u;
m_ptr_LREffet_donnee = new float[m_taille];
for(int i=0;i<m_taille;i++) { m_ptr_LREffet_donnee[i] = 0.; }
m_ptr_LREffet_courant = m_ptr_LREffet_donnee;
m_ptr_LREffet_fin = (m_ptr_LREffet_donnee + m_taille-1);
}
//----------------------------------------------------------------------------------------------------------------------------------LigneRetard::~LigneRetard () {}
//----------------------------------------------------------------------------------------------------------------------------------void LigneRetard::Insere(float valeur_u)
{
*m_ptr_LREffet_courant = valeur_u ;
}
//----------------------------------------------------------------------------------------------------------------------------------void LigneRetard::Propage()
{
if(m_ptr_LREffet_courant != m_ptr_LREffet_fin) m_ptr_LREffet_courant++ ;
else if (m_ptr_LREffet_courant == m_ptr_LREffet_fin) m_ptr_LREffet_courant = m_ptr_LREffet_donnee ;
else cout << "Il y a eu un problème dans la propagation du pointeur courant sur la ligne à Retard !" << endl;
}
//-----------------------------------------------------------------------------------------------------------------------------------
16
40
45
50
float LigneRetard::RetourneValeurCourante ()
{
return *m_ptr_LREffet_courant ;
}
//----------------------------------------------------------------------------------------------------------------------------------float LigneRetard::RetourneValeurPasse (int d_u, float alpha_u)
{
float resu = *(m_ptr_LREffet_courant - d_u) * alpha_u + *(m_ptr_LREffet_courant) ;
return resu;
}
//----------------------------------------------------------------------------------------------------------------------------------void LigneRetard::NettoieLigneRetard()
{
for(int i=0;i<m_taille;i++) m_ptr_LREffet_donnee[i] = 0. ;
}
//-----------------------------------------------------------------------------------------------------------------------------------
Décrivons maintenant les méthodes qui font du buer circulaire une ligne à retard. Le constructeur de la
classe LigneRetard, tout d'abord, est assez simple pour le pas être décrit. Précisons tout de même que la taille
du buer est choisie par l'utilisateur, nous avons pris lors de la LigneRetard LignaRetardo (dans
M ain.cpp)
une valeur de 441000, soit la taille de dix tables d'onde, ce qui permet de générer des délais assez longs.
Insere(f loat valeur_u) permet d'entrer dans le buer circulaire une valeur de son, à l'emplaP ropage() déplace le pointeur courant sur l'emplacement suivant du buer. La
méthode RetourneV aleurCourante() fait le contraire de Insere(f loat valeur _u) puisqu'elle permet de retourner la valeur courant enregistrée dans la ligne à retard tandis que la méthode RetourneV aleurP asse(...)
La fonction
cement courant. La méthode
retourne la somme d'une valeur passée avec la valeur courante, cette somme subissant une certaine normalisation en atténuant les deux valeurs (pour éviter la divergence du signal avec le delais). Le paramètre
alpha_u, est choisi par l'utilisateur, et dans notre cas, l'utilisateur sera la fonction
ActualiseLigneRetardEtRenvoieOut() de la classe Filtre. Enn, la fonction N ettoieLigneRetard() permet de
de normalisation, noté
remettre à zéro tout les emplacements du buer circulaire.
5.2 Les eets.
La ligne à retard étant maintenant expliquée, présentons les trois types d'eets réalisés dans ce pro jet :
SimpleDelay : on a joute au signal sa valeur jouée
d samples plus tôt.
La ligne à retard ne contient que des
valeurs reçues en entrée.
FeedBackDelay : c'est un délais avec une boucle de rétroaction, i.e. qu'on rentre dans la ligne à retard les
valeurs de sortie. Ainsi, la valeur du signal de sortie dépend maintenant à la fois des entrées, et à la fois
des sorties du passé.
PassAllFilter : c'est l'association du SimpleDelay avec le FeedBackDelay de sorte à ne pas avoir de modication de l'amplitude du signal (réponse plate en fréquence), mais une modication en phase.
Ces trois eets sont présentés schématiquement en gure 4.
1
5
enum TypeEffet {SimpleDelay, FeedBackDelay, PassAllFilter} ;
class LigneRetard;
class Effet
{
private:
TypeEffet m_TypeEffet;
bool m_On;
10
15
};
public:
Effet ();
~Effet ();
TypeEffet get_TypeEffet();
float ActualiseLigneRetardEtRenvoieOut(LigneRetard & LignaRetardo
,TypeEffet TypeEffet_u,float temp_out,int delay_u);
bool get_state();
void SwitchStateEffet();
void Change_TypeEffet(TypeEffet TypeEffet_u) { m_TypeEffet = TypeEffet_u ; }
On dénit donc simplement un type d'eet pour la classe Eet et une variable d'état de l'Eet (actif ou
inactif ).
1
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
17
(a) SimpleDelay
(b) FeedBackDelay
(c) AllPassFilter
Fig. 4: Les trois types d'eets étudiés.
5
10
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
#include <fstream>
using namespace std;
#endif
// pour stocker dans un fichier
#include "LigneRetard.h"
#include "Effet.h"
15
20
25
30
35
//----------------------------------------------------------------------------------------------------------------------------------Effet::Effet()
{
m_TypeEffet = SimpleDelay;
m_On = 0 ;
}
//----------------------------------------------------------------------------------------------------------------------------------Effet::~Effet() {}
//----------------------------------------------------------------------------------------------------------------------------------TypeEffet Effet::get_TypeEffet()
{
return m_TypeEffet ;
}
//----------------------------------------------------------------------------------------------------------------------------------float Effet::ActualiseLigneRetardEtRenvoieOut(LigneRetard & LignaRetardo_u,TypeEffet TypeEffet_u,float temp_out,int delay_u)
{
float resu_out = 0.;
float alpha_u = 0.85; // voir après pour le mettre en argument
switch (TypeEffet_u)
{
case SimpleDelay:
LignaRetardo_u.Insere(temp_out * (1.-alpha_u));
18
case FeedBackDelay:
40
45
case PassAllFilter :
50
55
60
65
}
return resu_out;
resu_out = LignaRetardo_u.RetourneValeurPasse(delay_u, alpha_u);
LignaRetardo_u.Propage();
break;
LignaRetardo_u.Insere(temp_out * (1.-alpha_u)); // On met temp_out dans la LR pour que
// RetourneValeurPasse puisse faire la somme du nouveau avec l'ancien signal
resu_out = LignaRetardo_u.RetourneValeurPasse(delay_u, alpha_u); // Somme
LignaRetardo_u.Insere(resu_out); // resu_out écrase temp_out dans la LR
LignaRetardo_u.Propage(); // propagation dans la LR
break;
LignaRetardo_u.Insere(temp_out);
resu_out = LignaRetardo_u.RetourneValeurPasse(delay_u, alpha_u);
resu_out -= temp_out;
LignaRetardo_u.Insere(resu_out);
resu_out = LignaRetardo_u.RetourneValeurPasse(delay_u, alpha_u);
break;
}
//----------------------------------------------------------------------------------------------------------------------------------bool Effet::get_state()
{
return m_On;
}
//----------------------------------------------------------------------------------------------------------------------------------void Effet::SwitchStateEffet()
{
if ( m_On == 0 ) m_On = 1 ;
else if ( m_On == 1 ) m_On = 0 ;
}
//-----------------------------------------------------------------------------------------------------------------------------------
Globalement, les fonctions utilisées pour cette classe ont toutes été décrites par une fonction équivalente
parmi les autres classes. Quant à la fonction
ActualiseLigneRetardEtRenvoieOut(...),
elle contient la valeur
du coecient d'atténuation du signal passé et un switch qui diérentie les eets. Les schéma de la gure 4 nous
permettent aussi de comprendre les instructions du switch.
5.3 Quelque résultats sur les eets.
Pour nir sur les eets, nous allons présenter en gure 5 quelque images d'eets obtenus sur le signal. En
particulier, on utilisera des notes diérentes pour que les composantes passées du signal aient des fréquences
diérentes. Les trois premières images montrent l'utilisation des trois eets, les photos étant prises en régime
transitoire entre deux notes. Ainsi, on arrive à trouver un moment où le AllPassFilter ne donne pas une réponse
totalement plate en fréquence (du fait du changement de note), mais il le devient après et la note garde la même
allure que l'oscillateur, avec une amplitude un peu atténuée. Quand à la quatrième elle montre l'utilisation de
la note de fréquence variable ('h') avec les eets. On voit que la complexité en fréquence du signal peut donner
un son modié par l'eet d'une allure très aléatoire mais ayant toujours une certaine periodicité. La valeur du
coecient d'atténuation prise ici est de
0.85.
Pour cette valeur, les notes ont une résonance cristalline et assez
mélodieuse.
6 La fonction portAudioCallback2 : le centre névralgique du programme.
Une callback est un fonction que le programme appelle automatiquement à chaque fois qu'un certain évènement de produit dans le programme. Dans notre cas, il s'agit de la création (et le jeu) d'un son. Autrement dit
la carte son appelle la callback à chaque fois qu'elle a besoin de faire une son, ce qu'elle fait automatiquement
tout les
de
BU F F ER_SIZE
SAM P LER AT E
secondes. En fait, la carte son ne demande pas une seule valeur de son, mais un nombre
f ramesP erBuf f er = 512
valeurs (ou samples).
On présente ici les dénitions de la fonction
8
portAudioCallBack2.
La fonction
Inf oM atos()
n'est pas
présentée car elle n'a pas été utilisée .
1
//////////////// Prototype /////////////////////////////////////////////////
bool SDL_event(bool *quit, Oscillateur & Oscillo, Note & Nota, Filtre & Filtra,
Signal & Signalo, LigneRetard & LignaRetardo, Effet & Effecto);
5
int InfoMatos(void);
8 Il
existe aussi dans le chier
F onctions.cpp
une troisième fonction qui est l'ancienne callback utilisée en tout début de pro-
gramme. Cette fonction crée seulement un son analytique (sans table d'onde) et je ne la décrirai donc pas.
19
(a) SimpleDelay
(b) FeedBackDelay
(c) AllPassFilter
(d) SimpleDelay avec note variable
Fig. 5: Quelque exemples d'eets sonores.
#define ZEROLINE 400
#define DELAY 14000 // définit le délay pour la ligne à retard associée aux effets
10
15
20
25
30
35
40
45
50
55
60
/////////////////////////////Callback 2//////////////////////////////////////////
/** J'introduit cette deuxième Callback qui elle est conforme à l'utilisation de la Table d'Onde.**/
static int portAudioCallback2( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags,
void *userData)
{
// ContreType (cast en anglais) le format du tableau d'entree de void vers float
float *out = (float*)outputBuffer;
// ContreType (cast en anglais) le format du tableau de sortie de void vers float
(void) inputBuffer; /* Prevent unused variable warning. */
///////////////////////// Note ACTIVE //////////////////////////////////////////////////////////////
if( Oscillo.get_state(Nota)!=0 )
{
float Modulation_Enveloppe = Enveloppa.EvalueADSR(Oscillo.get_temps(Nota));
//
cout << "ooo " << Oscillo.get_temps(Nota) << endl;
// on a framesPerBuffer = 512 (pour mémoire)
//////////////////////////// FILTRE INACTIF /////////////////////////////////////
if (Filtra.get_filtre_state() == 0)
{
float Frequence_Note = Oscillo.get_Note(Nota);
for(int i=0; i < framesPerBuffer; i++ )
{
//////////// SANS EFFETS ////////////////
if(Effecto.get_state()==0)
{
// ----- Canal Gauche ----*out++ = Oscillo.get_m_out() * Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
Oscillo.ActualiseSortieTableOnde();
// ----- Canal Droit ----if(Oscillo.get_IsCanalDroitActive()==1)
{
*out++ = Oscillo.get_m_out() * Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
Oscillo.ActualiseSortieTableOnde();
}
else if(Oscillo.get_IsCanalDroitActive()==0)
{
*out++ = 0.;
Oscillo.ActualiseSortieTableOnde();
}
}
//////////// AVEC EFFETS ////////////////
float temp_out;
if(Effecto.get_state()==1)
{
// ----- Canal Gauche ----temp_out = Oscillo.get_m_out() * Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
*out++ = Effecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo,FeedBackDelay,temp_out,DELAY);
Oscillo.ActualiseSortieTableOnde();
20
// ----- Canal Droit ----if(Oscillo.get_IsCanalDroitActive()==1)
{
temp_out = Oscillo.get_m_out() * Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
*out++ = Effecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo,FeedBackDelay,temp_out,DELAY);
Oscillo.ActualiseSortieTableOnde();
}
else if(Oscillo.get_IsCanalDroitActive()==0)
{
temp_out = 0.;
*out++ = Effecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo,FeedBackDelay,temp_out,DELAY);
Oscillo.ActualiseSortieTableOnde();
}
65
70
75
}
/////////////////////////////////////////////
Signalo.Actualise_Signal_Buffer(*out,i);
// Commenter cette ligne permet d'avoir l'image que quand on relache la touche
Oscillo.ActualiseIndexTableOnde(Frequence_Note);
80
}
}
//////////////////////////// FILTRE ACTIF /////////////////////////////////////
else if (Filtra.get_filtre_state() == 1)
{
float Frequence_Note = Oscillo.get_Note(Nota);
float Q_variable = Oscillo.get_Q_variable(Filtra,Nota);
float FreqCoupure_variable = Oscillo.get_FreqCoupure_variable(Filtra,Nota);
85
90
for(int i=0; i < framesPerBuffer; i++ )
{
//////////// SANS EFFETS ////////////////
if(Effecto.get_state()==0)
{
// ----- Canal Gauche ----*out++ = Filtra.calcul_out(FreqCoupure_variable,Q_variable,Oscillo.get_m_out())
* Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
Oscillo.ActualiseSortieTableOnde();
95
100
105
110
}
//////////// AVEC EFFETS ////////////////
float temp_out;
if(Effecto.get_state()==1)
{
// ----- Canal Gauche ----temp_out = Filtra.calcul_out(FreqCoupure_variable,Q_variable,Oscillo.get_m_out())
* Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
*out++ = Effecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo,FeedBackDelay,temp_out,DELAY);
Oscillo.ActualiseSortieTableOnde();
115
120
// ----- Canal Droit ----if(Oscillo.get_IsCanalDroitActive()==1)
{
temp_out = Filtra.calcul_out(FreqCoupure_variable,Q_variable,Oscillo.get_m_out())
* Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
*out++ = Effecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo,FeedBackDelay,temp_out,DELAY);
Oscillo.ActualiseSortieTableOnde();
}
else if(Oscillo.get_IsCanalDroitActive()==0)
{
temp_out = 0.;
*out++ = Effecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo,FeedBackDelay,temp_out,DELAY);
Oscillo.ActualiseSortieTableOnde();
}
125
130
135
140
}
145
// ----- Canal Droit ----if(Oscillo.get_IsCanalDroitActive()==1)
{
*out++ = Filtra.calcul_out(FreqCoupure_variable,Q_variable,Oscillo.get_m_out())
* Oscillo.get_intensite(Nota) * Modulation_Enveloppe;
Oscillo.ActualiseSortieTableOnde();
}
else if(Oscillo.get_IsCanalDroitActive()==0)
{
*out++ = 0.;
Oscillo.ActualiseSortieTableOnde();
}
}
}
}
Signalo.Actualise_Signal_Buffer(*out,i);
Oscillo.ActualiseIndexTableOnde(Frequence_Note);
///////////////////////// Note INACTIVE //////////////////////////////////////////////////////////////
if( Oscillo.get_state(Nota)==0 )
{
*out = 0;
21
for(int i=0; i <
{
// ----*out++ =
// ----*out++ =
150
155
160
165
170
175
180
185
}
}
framesPerBuffer; i++ )
Canal Gauche ----0;
Canal Droit ----0;
Signalo.Actualise_Signal_Buffer(*out,i);
// rigolo : quand on commente cette ligne, on a après relachement de la note
// l'affichage qui prend la partie réelle et qui se fige (photo).
pixel ZeroLinePixel (80,ZEROLINE,1020,2,125,254,125);
//////////// SIGNAL ACTIF ////////////////
if(Signalo.Get_EtatSignal()==1)
{
AfficherPixel(&ZeroLinePixel,1); // ligne du zéro d'amplitude
Signalo.Afficher_signal();
Signalo.RemetlesPixelsAZero();
//
if(Nota.get_TouchePrecedente()!='w')
//
{
//
SDL_Surface *Background = SDL_SaveBMP(SDL_Surface *screen, "image.bmp");
//
SDL_SaveBMP(SDL_Surface *screen, "image.bmp");
//
}
AfficherFond(255,255,255); // Agit comme si on effaçait l'écran.
}
//////////// SIGNAL INACTIF ////////////////
else if(Signalo.Get_EtatSignal()==0)
{
//
ActualiserEcran();
// Utile en théorie, mais fait perdre de la qualité au son en pratique (processus qui demande beaucoup au pc)
}
return 0;
}
/////////////////////////////////Fin Callback 2///////////////////////////////////
La fonction
portAudioCallBack2
reçoit six arguments dont seuls le second et le troisième nous intéressent
∗outputBuf f er
f ramesP erBuf f er est
réellement pour décrire le programme :
est un pointeur sur un buer (dynamique) recevant les
valeurs qu'attend la carte son,
ce nombre de valeurs qu'attend le buer. La fonction
comporte diérents cas, suivant que la Note est active ou non, et suivant que le ltre ou les eets sont activés
ou non. En n de fonction on traite aussi l'achage du signal, suivant qu'il est actif ou non. L'achage sera
décrit plus tard, dans la section 7.1.
Prenons tout d'abord le cas Note active, ltre inactif, sans eets. Comme dans tout les cas, le principe
va être de remplir le buer avec les 512 valeurs. C'est donc par une boucle for que l'on procède, et la valeur
calculée à chaque tour de boucle est sortie par le pointeur out, que l'on incrémente en écrivant out++ et en
lui attribuant une aleur. On écrit en eet
M odulation_Enveloppe;
∗out + + = Oscillo.get_m_out() ∗ Oscillo.get_intensite(N ota) ∗
car le son joué doit être composé du signal de l'oscillateur (dont l'amplitude vaut
1), multiplié par l'amplitude de la note (toujours égale à 1 mais on peut voir dans
SDL_clavier.cpp
qu'il
est possible d'attribuer une amplitude moindre à certaine notes, ce qui donne plus de généralité au programme), et multiplié par l'amplitude de l'enveloppe ADSR. On distingue bien sûr les cas canal droit actif et canal droit inactif et on utilise
Oscillo.ActualiseSortieT ableOnde()
pour faire avancer le pointeur
de la table d'onde de l'oscillateur. A la n du tour de boucle, on actualise l'index de la table d'onde avec
Oscillo.ActualiseIndexT ableOnde(F requence_N ote) et
7.1 qu'est Signalo.Actualise_Signal _Buf f er(∗out, i).
on ache le signal avec la fonction décrite en section
Considérons maintenant le cas Note active, ltre actif, sans eets. Le schéma général est le meme, seule
change l'expression de la valeur de sortie. On a en eet :
*out++ = Filtra.calcul\_out(FreqCoupure\_variable,Q\_variable,Oscillo.get\_m\_out())
* Oscillo.get\_intensite(Nota) * Modulation\_Enveloppe;
avec
F iltra.calcul_out(F reqCoupure_variable, Q_variable, Oscillo.get_m_out()) la fonction qui s'occupe
de ltrer le signal donné par l'oscillateur et qui a été décrite en 4.2. Bien sûr, on multiplie ce signal par l'intensité
de la note et l'enveloppe ADSR.
Enn, les cas Note active, ltre inactif, avec eets et Note active, ltre actif, avec eets peuvent être
temp_out qui a la même
∗out + + précédement décrite (dans les deux cas respectifs) et on envoie cette valeur à la
fonction Ef f ecto.ActualiseLigneRetardEtRenvoieOut(LignaRetardo, F eedBackDelay, temp_out, DELAY )
qui donne la valeur ∗out + + à jouer. Bien sûr, cette fonction a d'autres arguments : LignaRetardo est l'adresse
traités en même temps. Pour traiter ce cas, on passe par une variable intermédiaire
expression que
22
de la ligne à retard utilisée par la classe Eet,
F eedBackDelay
est la valeur par défaut du délais (i.e. un
délais avec boucle de rétroaction, cf. 5.2) mais que l'utilisateur pourra changer par l'intermédiaire du clavier, et
DELAY
qui est un entier donnant le delais à utiliser pour créer l'eet.
7 Les autres classes et fonctions utilisées pour l'achage du signal et
l'interface clavier.
Nous allons décrire dans cette partie les diérents chiers du code source utilisés pour acher le signal à
l'écran, par l'intermédiaire de la librairie SDL, ainsi que ceux qui nous permettent l'interfaçage clavier.
7.1 L'achage par SDL du signal.
7.1.1 Le pixel.
L'ensemble des classes (structures) et méthodes utilisés pour gérer les pixels sont d'une grande simplicité
et sont assez explicites pour ne pas être décrites. Nous nous contenterons donc d'en donner le code source.
Précisons juste que le pixel est entièrement déni par sa position (structure point), sa taille, et sa couleur
(structure couleur) que l'on regroupe dans la classe point. Les méthodes de cette classe agissent essentiellement
sur sa taille et sa position en
1
5
10
y.
#ifndef _PIXEL
#define _PIXEL
class pixel
{
public :
point Position;
int dimX;
int dimY;
couleur couleurRGB;
pixel();
pixel (int abs_u, int ord_u, int dimX_u, int dimY_u, int c1_u, int c2_u, int c3_u);
15
20
};
~pixel () {}
void DeplacePixel (int x,int y) { Position.abs += x; Position.ord += y; }
void set_dimY (int dimY_u) { dimY = dimY_u; }
int get_dimY() { return dimY ; }
void set_OrdonneePixel (int OrdonneePixel_u,int offset) { Position.ord = OrdonneePixel_u + offset; }
#endif
1
5
#include
#include
#include
#include
#include
#include
<stdio.h>
<math.h>
<iostream>
<SDL/SDL.h>
<stdlib.h>
<fstream>
// pour stocker dans un fichier
using namespace std;
10
15
20
25
30
#include "SDL_Pixel_Mag.h"
#include "pixel.h"
#include "couleur.h"
pixel::pixel()
{
Position.abs = 0 ;
Position.ord = 0 ;
dimX = 1 ;
dimY = 1 ;
couleurRGB.Ini(125,0,0);
}
pixel::pixel (int abs_u, int ord_u, int dimX_u, int dimY_u, int c1_u, int c2_u, int c3_u)
{
Position.abs = abs_u ;
Position.ord = ord_u ;
dimX = dimX_u ;
dimY = dimY_u ;
couleurRGB.Ini(c1_u,c2_u,c3_u);
}
23
1
5
10
#ifndef _COULEUR
#define _COULEUR
typedef struct
{
Uint8 Rouge;
Uint8 Vert;
Uint8 Bleu;
void Ini(Uint8 r, Uint8 g, Uint8 b) {Rouge = r; Vert = g; Bleu = b;}
}couleur;
#endif
1
5
10
#ifndef _POINT
#define _POINT
typedef struct
{
int ord;
int abs;
}point;
#endif
7.1.2 Le signal, un ensemble de pixels.
Par dénition, le signal va être un ensemble de pixels enregistrés dans un tableau dynamique.
1
5
class pixel;
class Signal
{
private:
pixel * TabPixels ;
pixel * TabPixels_debut;
bool m_EtatDuSignal;
10
15
};
public:
Signal();
~Signal();
void Actualise_Signal_Buffer(double sound_out, int IndexTabPixels);
void Afficher_signal();
void RemetlesPixelsAZero();
void SwitchAfficherSignal();
bool Get_EtatSignal();
20
On dénit dans cette classe un pointeur courant
T abP ixels
(ainsi que le pointeur
T abP ixels_debut)
sur
un pixel pour créer un tableau de pixels qui va nous permettre d'acher le signal sonore à l'écran. La valeur
booléenne
1
5
10
15
m_EtatDuSignal
permet de coder l'état de l'ob jet de la classe Signal.
#ifndef HEADERS_DE_BASE
#define HEADERS_DE_BASE
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <SDL/SDL.h>
#include <stdlib.h>
#include <fstream>
using namespace std;
#endif
#include
#include
#include
#include
#include
"couleur.h"
"point.h"
"pixel.h"
"Signal.h"
"SDL_Pixel_Mag.h"
#define BUFFER_SIZE 512
24
20
#define PixelHauteurMax 150
#define OFFSET 0 //1*PixelHauteurMax
#define ZEROLINE 400
25
30
35
40
45
//----------------------------------------------------------------------------------------------------------------------------------Signal::Signal()
{
TabPixels = new pixel[BUFFER_SIZE];
for(int i=0;i<BUFFER_SIZE;i++)
{
TabPixels[i] = pixel(350+i*1,ZEROLINE,2,0,0,0,0);
}
m_EtatDuSignal = 0;
}
/* Si j'ajoute plus tard un pixel en argument du constructeur de pixel, alors il faudra redéfinir l'opérateur d'affectation =.
Ici, = joue comme un opérateur d'affectation d'un pixel. */
//----------------------------------------------------------------------------------------------------------------------------------Signal::~Signal() {}
//---------------------------------------------------------------------------------------------------------------------------------void Signal::Actualise_Signal_Buffer(double sound_out, int IndexTabPixels)
{
int HauteurPixel = (int)(sound_out * PixelHauteurMax) ;
if((HauteurPixel < (-10) * PixelHauteurMax)||(HauteurPixel > 10 * PixelHauteurMax)) HauteurPixel = 0;
// Astuce: on coupe les valeurs trop grandes qui font planter l'affichage par SDL et on les met à zéro.
// Ce qui ne s'entend pas trop car elle ne se suivent pas.
// gestion de la hauteur des pixels, hauteur algébrique
// et gestion de l'algébricité par le déplacement de l'origine du pixel
if(IndexTabPixels<BUFFER_SIZE)
{
if(HauteurPixel<0)
{
TabPixels[IndexTabPixels].DeplacePixel(0,HauteurPixel) ;
TabPixels[IndexTabPixels].set_dimY((-1)*HauteurPixel);
}
else { TabPixels[IndexTabPixels].set_dimY(HauteurPixel); }
IndexTabPixels++;
}
50
55
60
65
70
75
80
85
90
// encodage de la couleur des pixels
TabPixels[IndexTabPixels].couleurRGB.Ini(HauteurPixel*1+1,HauteurPixel*3+3,HauteurPixel*5+5);
TabPixels[IndexTabPixels].couleurRGB.Ini(254,254,254);
//
}
//----------------------------------------------------------------------------------------------------------------------------------void Signal::Afficher_signal()
{
for(int i=0;i<BUFFER_SIZE;i++)
{
AfficherPixel(&TabPixels[i],1);
}
ActualiserEcran();
/* ATTENTION : SDL fait que lorsque l'on a une valeur positive de l'intensité, alors le signal est vers le bas !
On aura donc à l'affichage un signal symétrique par rapport à l'horizontale (I=0) du signal donné dans la Table d'Onde. */
}
//----------------------------------------------------------------------------------------------------------------------------------void Signal::RemetlesPixelsAZero()
{
for(int i=0;i<BUFFER_SIZE;i++) TabPixels[i].set_OrdonneePixel(ZEROLINE,OFFSET);
}
//---------------------------------------------------------------------------------------------------------------------------------void Signal::SwitchAfficherSignal()
{
if ( m_EtatDuSignal == 0 ) m_EtatDuSignal = 1 ;
else if ( m_EtatDuSignal == 1 ) m_EtatDuSignal = 0 ;
}
//---------------------------------------------------------------------------------------------------------------------------------bool Signal::Get_EtatSignal()
{
return m_EtatDuSignal;
}
//----------------------------------------------------------------------------------------------------------------------------------
Le constructeur permet de créer le tableau dynamique de pixels et d'initialiser les pixels grâce au constructeur
de pixel qui reçoit en argument les coordonnées, les dimensions et les trois variables de couleur nécessaires. La
fonction
Actualise_Signal_Buf f er(double sound_out, int IndexT abP ixels)
a déjà été énoncée en 6 et son
rôle est d'acher le signal. Cette fonction reçoit la valeur du son à acher ainsi qu'un indice, indice qui va
correspondre à sa place dans le tableau de pixels. Comme la taille d'un pixel est une valeur entière, on dénit
HauteurPixel comme la valeur du son (qui appartient à
[0, 1]) multipliée par une hauteur de pixel maximale (ici
150). Ayant constaté que quelque valeurs divergeaient pas endroits, j'ai inclu une condition pour ne pas acher
25
le signal dans ces cas, ce qui évite un plantage inopiné de SDL. Cette fonction s'occupe ensuite de donner une
hauteur au pixel qui corresponde à la valeur HauteurPixel. Bien sûr, comme la taille d'un pixel est positive, il
faut traiter deux cas :
le signal est positif : on prend donc la valeur de HauteurPixel pour sa taille en
y
(verticale).
le signal est négatif : on prend l'opposée de la valeur HauteurPixel et on déplace l'origine en
y
du pixel
d'autant.
Il faut noter que les coordonnées d'un pixel augmentent en allant de gauche à droite en
en
y.
x,
et de haut en bas
Une valeur du signal positive sera donc achée vers le bas. On pourrait changer cela en inversant les cas,
mais comme nos signaux sont symétriques par rapport à l'axe des
x,
cela n'a aucune importance. Aussi, il faut
noter que la façon de remplir le tableau et de l'acher entraine que les nouvelles valeurs du signal apparaissent
à gauche. Le signal parrait donc se déplacer vers la gauche de l'écran. Ce qui est aché à l'écran ressemble donc
à une vidéo d'une onde innie qu'on lmerait en se déplacant vers la droite à une certaine vitesse. Enn, on
dénit la couleur des pixels selon un dégradé qui dépend de leur hauteur, et ce juste pour l'aspect esthétique.
La fonction Af f icher _signal() ache les pixels du tableau de pixels et actualise l'écran. La fonction
RemetlesP ixelsAZero() permet de remettre les pixels à zéro. On notera que l'on peut jouer, avec cette fonction,
sur une composante continu du signal (OF F SET ) qui nous éloignerait d'une valeur de référence (donnée par
ZEROLIN E ). Les deux autres fonctions sont assez évidentes pour ne pas être décrites.
Enn, deux autres chiers ont été utiles dans ce projet, ce sont SDL_P ixel _M ag.h et SDL_P ixel _M ag.cpp.
Pour limiter la taille de ce document, et puisqu'ils ne sont pas le résultat de mon travail, je n'en parlerai pas
d'avantage.
7.2 L'interface clavier SDL.
Elle est gérée par un unique chier :
1
5
10
15
20
25
30
35
40
45
SDL_clavier.cpp.
#include <SDL/SDL.h>
#include <iostream>
using namespace std;
#include
#include
#include
#include
#include
#include
"Oscillateur.h"
"Note.h"
"Filtre.h"
"Signal.h"
"LigneRetard.h"
"Effet.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
bool SDL_event(bool *quit, Oscillateur & Oscillo, Note & Nota, Filtre & Filtra, Signal & Signalo,
LigneRetard & LignaRetardo, Effet & Effecto)
{
SDL_Event event;
while( SDL_PollEvent( &event ) ){
switch( event.type ){
case SDL_KEYDOWN:
/** Au toucher de la touche **/
switch( event.key.keysym.sym ){
case SDLK_a:
cout << "......... a .........." << endl; // La
Nota.set_frequence('a'); Nota.set_state(1);
// Nota.set_intensite(0.5); // La note La sera deux fois moins intense que les autres !
break;
case SDLK_b:
cout << "......... b .........." << endl; // Si
Nota.set_frequence('b'); Nota.set_state(1);
break;
case SDLK_c:
cout << "......... c .........." << endl; // do
Nota.set_frequence('c'); Nota.set_state(1);
break;
case SDLK_d:
cout << "......... d .........." << endl; // re
Nota.set_frequence('d'); Nota.set_state(1);
break;
case SDLK_e:
cout << "......... e .........." << endl; // mi
Nota.set_frequence('e'); Nota.set_state(1);
break;
case SDLK_f:
cout << "......... f .........." << endl; // fa
Nota.set_frequence('f'); Nota.set_state(1);
break;
case SDLK_g:
cout << "......... g .........." << endl; // sol
Nota.set_frequence('g'); Nota.set_state(1);
break;
case SDLK_h:
cout << "......... h (note variable) .........." << endl; // sol
Nota.set_frequence('h'); Nota.set_state(1);
break;
26
50
55
case SDLK_m:
case SDLK_l:
cout << "******** Change
Nota.set_frequence('m');
break;
cout << "******** Change
Nota.set_frequence('l');
break;
Q du Filtre ********" << endl;
Nota.set_state(1);
Fréquence Coupure ********" << endl;
Nota.set_state(1);
case SDLK_x:
cout << "......... Filtre ..........." << endl;
Filtra.switch_state();
break;
case SDLK_p:
cout << "..... Nbr de Canaux ....." << endl;
Oscillo.Change_IsCanalDroitActive();
break;
case SDLK_w:
cout << "======== Affichage/Fermeture du Signal ========" << endl;
Signalo.SwitchAfficherSignal();
break;
70
case SDLK_s:
cout << "======== Activer/Desactiver Effet ========" << endl;
Effecto.SwitchStateEffet();
break;
75
case SDLK_F1:
cout << "******** LowPassFilter ********" << endl;
Filtra.Change_TypeFiltreAFreqCoupCste(LowPassFilter);
break;
cout << "******** HighPassFilter ********" << endl;
Filtra.Change_TypeFiltreAFreqCoupCste(HighPassFilter);
break;
cout << "******** BandPassFilter ********" << endl;
Filtra.Change_TypeFiltreAFreqCoupCste(BandPassFilter);
break;
cout << "******** Notch ********" << endl;
Filtra.Change_TypeFiltreAFreqCoupCste(Notch);
break;
cout << "******** AllPassFilter ********" << endl;
Filtra.Change_TypeFiltreAFreqCoupCste(AllPassFilter);
break;
60
65
case SDLK_F2:
80
85
case SDLK_F3:
case SDLK_F4:
case SDLK_F5:
90
95
case SDLK_F6:
case SDLK_F7:
case SDLK_F8:
100
105
110
case SDLK_F9:
case SDLK_F10:
case SDLK_F11:
case SDLK_F12:
115
cout << "******** SimpleDelay Effect ********" << endl;
Effecto.Change_TypeEffet(SimpleDelay);
break;
cout << "******** FeedBackDelay Effect ********" << endl;
Effecto.Change_TypeEffet(FeedBackDelay);
break;
cout << "******** PassAllFilter Effect ********" << endl;
Effecto.Change_TypeEffet(PassAllFilter);
break;
cout << "******** sinusoide ********" << endl;
Oscillo.Change_TypeOnde(sinusoide);
Oscillo.RemplirTableOnde();
break;
cout << "******** carre ********" << endl;
Oscillo.Change_TypeOnde(carre);
Oscillo.RemplirTableOnde();
break;
cout << "******** triangle ********" << endl;
Oscillo.Change_TypeOnde(triangle);
Oscillo.RemplirTableOnde();
break;
cout << "******** bruit ********" << endl;
Oscillo.Change_TypeOnde(bruit);
Oscillo.RemplirTableOnde();
break;
case SDLK_q:
120
125
130
135
*quit = true;
break;
}// fin switch nom de touche
break;
/** Au relachement de la touche **/
case SDL_KEYUP:
switch( event.key.keysym.sym ){
case SDLK_a:
cout << "......... /a ........." << endl; // La
Nota.set_state(0);// Nota.set_intensite(1.);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_b:
cout << "......... /b ........." << endl; // Si
Nota.set_state(0);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_c:
cout << "......... /c ........." << endl; // do
Nota.set_state(0);
27
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_d:
cout << "......... /d ........." << endl; // re
Nota.set_state(0);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_e:
cout << "......... /e ........." << endl; // mi
Nota.set_state(0);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_f:
cout << "......... /f ........." << endl; // fa
Nota.set_state(0);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_g:
cout << "......... /g ........." << endl; // sol
Nota.set_state(0);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_h:
cout << "......... /h (note variable) .........." << endl; // sol
Nota.set_state(0);
// LignaRetardo.NettoieLigneRetard();
break;
case SDLK_m:
cout << "******** / Change Q du Filtre ********" << endl;
Nota.set_state(0); Filtra.RestaureQParDefaut();
LignaRetardo.NettoieLigneRetard();
break;
case SDLK_l:
cout << "******** Change Fréquence Coupure ********" << endl;
Nota.set_state(0); Filtra.RestaureFreqCoupureParDefaut();
LignaRetardo.NettoieLigneRetard();
break;
}// fin switch nom de touche
break;
140
145
150
155
160
165
170
175
}
case SDL_QUIT:
*quit = true;
break;
}// fin event type
}// fin pollEvent
Cette fonction est constituée de deux switchs, le premier gérant l'interface clavier pour les touches que l'on
presse, le deuxième celles que l'on relache. Comme on peut le voir, chaque note (correspondant à une lettre)
utilise les fonctions
set_f requence(...)
et
set_state(1)
pour attribuer la fréquence et activer l'état de la note
correspondante (gérée par l'ob jet Nota de la classe Note). Pour désactiver la note, on utilise aussi
set_state(),
mais cette fois-ci avec l'argument 0. Il faut aussi noter que l'on peut ici attribuer des intensités diérentes pour
les notes (voir commentaire du code source pour la note 'a'). Les touches 'x', 'p', 'w', et 's' permettent de
gérer les diérents modes grâce à des fonctions précédement décrites. Les touches 'F1' à 'F5' permettent de
sélectionner un type de ltre, celles de 'F6' à 'F8' de sélectionner un type d'eet, et enn celles de 'F9' à 'F12'
de sélectionner un type d'onde pour l'oscillateur. Notons enn l'utilisation de la fonction
N ettoieLigneRetard()
pour vider la ligne à retard lorsque l'on relache les notes. J'ai fait le choix de laisser cette fonction seulement
dans le cas des notes faisant varier le facteur de qualité du ltre et la fréquence de coupure du ltre. En eet,
garder les valeurs de la table d'onde des sons précédents permet de faire jouer deux notes en même temps. Ainsi,
il devient possible de jouer un son plus complexe en fréquence et en harmonique, i.e. un son plus riche.
28
8 Conclusion.
Pour conclure ce document, on peut dire que les principaux objectifs du synthétiseur de son ont été atteint.
On a en eet réussi à créer un signal modulé par la méthode de la table d'onde et à créer une enveloppe pour
donner au son une allure plus agréable. On a aussi pu ltrer ce son avec divers ltres ainsi que moduler leurs
paramètres. Dernièrement, on a pu créer quelque eets simples sur le son qui, même si nous somme encore loin
d'un vrai synthétiseur sonore, nous permettent d'entrevoir les concepts et les dicultés de telles réalisations.
Notamment, ce projet a permis de mettre en illustration l'importance d'un code ecace, pour éviter les méfaits
d'une mémoire et d'une capacité de traitement nies des ordinateurs. Dans le même esprit, il a montré que
la synthèse d'un son associé à un achage demande beaucoup de ressources pour l'ordinateur. Néanmoins,
ce programme n'est pas parfait. On peut citer deux points qui posent légèrement problème. Tout d'abord la
modulation des paramètres du ltre n'est pas faite de la manière la plus propre possible, mais c'est celle qui
pose le moins de problème dans un code qui est déjà assez lourd. Ensuite, il est apparu quelque divergeances
dans le signal lors de l'utilisation des eets. Ce problème a fait l'objet d'un grande attention de ma part, sans
aboutir sur une solution. J'ai alors contourné le problème en enlevant ces divergeantes, ce qui en résulte par de
légers cliquetis lors de l'utilisation des eets. Bien sûr, à ces deux petit soucis on pourrait ajouter le fait que
l'interface avec l'utilisateur est assez sommaire, mais l'utilisation du clavier tend à compenser ce manque. D'un
manière plus générale, ce pro jet m'a permis de compléter mes connaissances en analyse du signal et de mettre
en pratique les nouvelles compétences en C++ acquises durant l'année. Il m'a aussi permis d'en apprendre plus
sur le domaine de la synthèse sonore, domaine très riche en complexité.
29

Documents pareils