programmation [C]
le format wave
Il existe un très grand nombre de formats audio utilisés dont certains sont des formats propriétaires
et d'autres libres de droits. Parmi ces formats, on distingue 2 grandes familles :
- Les formats sans perte de données (WAV, AIFF, FLAC …).
- Les formats avec compression de données. Pour un espace de stockage réduit, le signal audio
est simplifié selon des algorithmes de compression avec pour coût une faible perte de qualité par
rapport au signal initial (MP3, AAC…).
Pour une intégration simplifiée dans notre programme, nous avons choisi le format « WAVE » pour son entête basique et son absence de compression.
Structure d’un fichier « WAVE »
On distingue 2 parties dans un fichier WAVE : l’entête et la partie DATA.
L’entête mesure 56 octets et contient les informations sur le format utilisé pour encoder le son
(nombre d’octets par point échantillonné, nombre de canaux, fréquence d’échantillonnage, longueur
du fichier…).
La partie DATA commence à l’octet 57 et contient toutes les valeurs des points échantillonnés.
Comment ouvrir un fichier WAVE dans un programme [C] ?
Il nous faut créer une structure (WAVFILE dans notre programme) rigoureusement identique
à celle de l’entête d’un fichier WAVE.
Charger le fichier dans le programme avec la commande « fopen ».
Dans le programme notre fichier audio est appelé WAV. On remarque aussi l’argument « rb » (read
binary) dans la commande « fopen ». Le « b » est ajouté pour permettre au compilateur [C] de
Windows de faire la distinction entre un fichier « TXT » et un fichier de données brutes.
Lecture de l’entête et initialisation du Header par la commande « fread ».
« fread » est une fonction permettant la lecture de données brutes (binaires) d’un fichier dans un
programme [C]. Il y a 4 paramètres à indiquer :
-La variable où les données seront enregistrées (tableau, structure ou variable simple)
-Taille des données à lire (en nombre d’octets)
-Nombre de fois que doit être effectuée la lecture
-Fichier à lire
Dans notre cas le programme va lire une fois le fichier WAV. Sa lecture se fera sur la taille du
HEADER (correspondant à celle de l’entête) et les données lues seront enregistrées dans le
HEADER.
Le HEADER ainsi rempli contient toutes les caractéristiques de notre fichier WAVE, notamment
la fréquence d’échantillonnage (header.frequency), le nombre de canaux (header.channels), le
nombre d’octets par échantillon (header.bytes_by_capture), le nombre d’octets dans la partie
DATA (header.bytes_in_data).
Les fichiers WAVE utilisés seront des mono (header.channels = 1).
On peut alors déterminer le nombre d’échantillons contenus dans la partie DATA (=header.bytes_in_data / header.bytes_by_capture).
Pour la suite de notre programme, « taille » correspondra à la puissance de 2 supérieure au nombre
total d’échantillons du fichier WAVE lu.
Création d’un tableau dynamique qui contiendra les échantillons.
On utilisera un tableau de type malloc, car la taille du tableau ne pourra être déterminée qu’après
exécution du programme.
Ce tableau est une matrice à 2 dimensions de 2 colonnes et « taille » lignes.
Il s’agit d’un tableau de nombres complexes, car nous verrons par la suite que l’algorithme de TF
utilisé dans nos autres programmes d'analyse nécessite un tableau de cette forme.
Enfin nous allons remplir ce tableau de nouveau avec la fonction « fread ».
Enfin ce tableau sera enregistré dans un fichier data
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <math.h> struct wavfile //définit la structure de l entete d un wave { char id[4]; // doit contenir "RIFF" int totallength; // taille totale du fichier moins 8 octets char wavefmt[8]; // doit etre "WAVEfmt " int format; // 16 pour le format PCM short pcm; // 1 for PCM format short channels; // nombre de channels int frequency; // frequence d echantillonage int bytes_per_second; // nombre de octets par secondes short bytes_by_capture; // nombre de bytes par echantillon short bits_per_sample; // nombre de bit par echantillon char data[4]; // doit contenir "data" int bytes_in_data; // nombre de bytes de la partie data }; main() { int i=0; int taille=1; //variable qui correspondra par la suite a la longueur du tableau(puissance de 2) int nbech=0; //nombre d echantillons extraits du fichier audio char fichieraudio[100]; printf ("entrer le nom du fichier audio a analyser :\n"); scanf("%s", fichieraudio); printf ("nom du fichier : %s\n", fichieraudio); FILE *wav = fopen(fichieraudio,"rb"); //ouverture du fichier wave struct wavfile header; //creation du header if ( wav == NULL ) { printf("\nne peut pas ouvrir le fichier demande, verifier le nom\n"); printf("ne pas oublier l'extention .wav\n"); exit(1); } //initialise le header par l'entete du fichier wave //verifie que le fichier possÈde un entete compatible if ( fread(&header,sizeof(header),1,wav) < 1 ) { printf("\nne peut pas lire le header\n"); exit(1); } if ( header.id[0] != 'R' || header.id[1] != 'I' || header.id[2] != 'F' || header.id[3] != 'F' ) { printf("\nerreur le fichier n'est pas un format wave valide\n"); exit(1); } if (header.channels!=1) { printf("\nerreur : le fichier n'est pas mono\n"); exit(1); } nbech=(header.bytes_in_data/header.bytes_by_capture); printf ("\nle fichier audio contient %d echantillons\n",nbech); while (nbech>taille) { taille=taille*2; puissance=puissance+1; } double **tab=NULL; //tableau de l'onde temporelle tab=malloc( (taille) * sizeof(double)); if (tab == NULL) { exit(0); } for(i=0;i<(taille);i++) { tab[i]=malloc( 2 * sizeof(double)); if (tab[i] == NULL) { exit(0); } } i=0; short value=0; FILE *dat=fopen("data.dat","w"); //fichier data des echantillons while( fread(&value,(header.bits_per_sample)/8,1,wav) ) { //lecture des echantillons et enregistrement dans le tableau tab[i][0]=value; i++; } printf("\nnombre d'echantillons lus : %d\n",i); printf("nombre de valeurs sauvegardees %d\n",i); for (i=0;i<taille;i++) { fprintf(dat,"%lf %lf\n", tab[i][0], tab[i][1]); } for(i=0;i<taille;i++) { free(tab[i]); tab[i] = NULL ; } fclose(wav); fclose(dat); } |
Nos programmes
Modes d'une pièce
Nous cherchons à calculer les premiers modes d'une pièce parallélépipédique dont nous connaissons les dimensions
à l'aide de la formule de la partie théorique, afin de prédire la réponse
impultionnelle de la salle étudiée.
figure 10 : Illustration de la densité modale en fonction de la fréquence (simulations numériques
traitant la salle étudiée dans les expériences 1 et 2)
télécharger le code source
Analyse spectrale
Pour une analyse efficace de nos echantillons sonores lors de nos diverses expériences nous avons eu recours à un algorythme de Transformée
de Fourier issus de "Numerical Reciepies in C". En effet un tel algorythme nous permet de traiter les données fréquencielles d'un son
dans un logiciel de tableur comme celui que nous utilisons : "Origine Pro 9". On peut trouver un outils d'analyse spectrale dans le logiciel
gratuit Audacity mais la résolution offerte n'est pas suffisante pour une mesure du RT60 par l'analyse de la largeur de pics modaux.
figure 12 : Mesure de la largeur d'un pic modal à -3dB
télécharger le code source
Longueur moyenne en 2D
Le but de ce programme est de vérifier la formule permettant de déterminer le parcours moyen d'un rayon entre deux réflections dans une salle
rectangulaire (salle en deux dimensions) :
AvecP le périmetre de la pièce rectangulaire en m et S la surface en m².
télécharger le code source
Filtre Welch
Il s'agit d'une version améliorée de l'algorythme de Transformée de Fourier précédent où le programme effectue la TF
d'un son, puis multiplie le résultat par une porte (fonction mathématique) pour enfin effectuer une Transformée de Fourier inverse.
Afin de limiter les résidus introduits par la Transfomée de Fourier inverse, nous utilisons une fenêtre de type Welch centrée sur la
fréquence du filtre passe bande souhaité :
AvecN le nombre de points constituant la largeur de la fenêtre.
figure 13 : Allure des différents types de fenêtre pour filtrage passe bande
figure 14 : Allure de la TF d'un delta de Dirac par les fenêtres présentant des résidus (lobes)
On remarque que lorsque l'on utilise une fenêtre Welch par rapport à une porte "carrée" on a une diminution des résidus
lors d'une transformée de Fourier ou sa transformée inverse.
télécharger le code source
On peut aussi accéder à un autre projet de licence "programmation d'un spectrogramme"