Pour effectuer une concaténation de fichier WAVE, nous avons impérativement de tableau de data ( son->data.data[i] ) définit dans la partie précédente. N’oubliez pas que la variable correspond son->data.data[i] correspond au i-ème samples (échantillons sonores). Pour le crossfade (amortissement) nous allons modifier la valeur réduire la valeur des samples de la fin du premier fichier, et augmenter celles du début du second fichier (et ainsi de suite). Pour l’overlap (fondu), nous allons additionner les X samples de fin du fichier avec les X samples de début de fichier suivant.
Cette fonction qui concatènera les différentes fichiers WAVE fait appel aux fonction définient dans les parties 1 & 2. Il faut donc inclure les fichiers sources (dans notre cas, nous avons des fichiers d’en-têtes .h contenant simplement les prototypes des fonctions contenuent dans les fichier source .c du même nom)
#include "Concat.h" #include "WAVE.h"
La fonction principale de cette partie fait appel a deux autres fonctions ( store et create_structure). La fonction store a pour but d’extraire les données de chaques fichiers WAVE et les stocker dans une strcuture appropriée; quant-à la fonction create_structure, elle remplie une structure à partir d’une structure d’un fichier WAVE déjà ouvert (nous verrons cette fonction dans la fin de cette partie).
Fonction store
Cette fonction appelle la fonction Read_Wav créer dans la partie précédente, elle possède 3 paramètres :
- path-tab Tableau de chaine de caractère contenant les repertoires des fichiers sons.
- file_struct Tableau de structures d’un fichier WAVE.
- nb_files Nombre de fichiers à recupérer les données.
Cette fonction aura deux indices permettant de gérer si un des fichiers est introuvables (cette fonctionnalité n’est cependant pas utilisée). La fonction n’étant pas très complexe est assez commentée, je ne vais pas m’attarder dessus
void store(char **path_tab, WAVE *file_struct, int nb_files){
// Indice pour parcourir l'ensemble des fichiers
int i = 0;
int j = 0;
// On parcours l'ensemble des fichiers
while (i < nb_files) {
// Appel de la fonction qui lit un fichier WAVE et stock les données dans uen structure
if (Read_Wav(path_tab[i], &file_struct[j]) == 0) { //reads file @i in tab d'analyse syntax and stores it in @j of struct tab file_struct
// Incrément l'indice j
j++;
} else {
// Fichier n'est pas présent / introuvable
printf("nerreur: fichier %s introuvablen", path_tab[i]);
printf("nsynthèse impossible");
exit(0);
}
// Incrément l'indice i
i++;
}
}
On constate bien que si un fichier n’est pas présent, au lieu de simplement l’ignorer et continuer le chargement, on arrêtera le programme, à vous de modifier en fonction de vos besoins.
Fonction concatenation
Cette fonction va stocker les données de chaque fichier WAVE, grâce à store(), puis va effectuer un cross-fade (fondu croisé) en modifiant les données de chaque echantillons. Les différents block de données vont être concaténés en subissant un overlap (chevauchement) sur 100 ms, puis ecris dans une structure de type WAVE, grâce à create_structure(), qui servira à écrire le fichier WAVE. Elle prendra en paramètre :
- path-tab, un tableau de chaine de caractère contenant les répertoires des fichiers sons à concaténer.
- sortie, une structure qui sera remplie avec les données des différents fichiers.
- nb_files, le nombre de fichiers à récupérer les données.
Dans un premier temps, nous allons définir un tableau de structure WAVE qui sera passé en paramètre à la fonction store qui le remplira.
// Declaration d'un tableau de strcuture où seront stocké les données. WAVE file_struct[nb_files]; // Appel à la fonction store() pout créer/remplir un tableau de structure store(path_tab, file_struct, nb_files);
Nous devons définir le laps de temps sur lequel nous devons effectuer l’overlap, puis calculer il correspond à combien d’échantillons.
// Definit le temps de l'overlap à 100 ms const float t_overlap = 0.01; // Calcul le nombre de sample sur lequel on va effectuer l'overlap à partir de la valeur eb ms (t_overlap) sur laquel on veut overlaper const float overlap = t_overlap * file_struct[0].fmt.SampleRate; //overlap time in samples
La prochaine étape consiste à calculer le nombre total d’échantillons sans se soucier de ceux supprimés par l’overlap. Pour cela on additionne le nombre d’échantillons (samples) de chaque fichier.
// Taille du tableau pour stocker samples
dword totalsize = 0;
int i;
/*
* Calcul de totalsize : on additione tous les nombres d'échantillons de chaque fichier
* sans prendre en compte l'overlap, afin d'être sûr de ne pas avoir un tableau trop petit
*/
for (i=0; i< nb_files; i++) {
totalsize += file_struct[i].data.Subchunk2Size / file_struct[i].fmt.Blockalign;
}
Le crossfade et overlap vont être effectué simultanément, c’est à dire que les fichiers qui subiront un crossfade seront par la suite additionner entre eux. Le crossfade se fait selon une fonction cosinus, à celle-c! on ajoute 1 afin qu’elle oscille entre 2 et 0.
La code parait à première vue assez complexe mais il est facilement compréhensible ( une fois digérée
). Etant donné les commentaires omniprésent je ne vois l’utilité de le détaillé, cependant si vous le désirez, n’hésitez pas à demander.
// Création du tableau pour stocker l'ensemble des échantillons
short* tab_sample;
tab_sample = malloc (totalsize * sizeof(short));
// Nombre d'échantillons dans le tableau final
unsigned int nb_sample = 0;
/*
* Copie l'intégralité des echantillons du premier fichier tab_struct[0],
* On parcourt le tableau de 0 jusqu'à la taille totale du nombre d'échantillons (nombre total de samples)
*/
for (nb_sample = 0; nb_sample < file_struct[0].data.Subchunk2Size / file_struct[0].fmt.Blockalign; nb_sample++) {
tab_sample[nb_sample] = file_struct[0].data.data[nb_sample];
}
// s: sample
int s = 0;
// On effectue un fade-in sur les premiers échantillons du premier fichier.
for (s = 0; s < overlap; s++) {
tab_sample[s] = (short) (tab_sample[s] * (1 - ( (1+cos( (s * M_PI) / overlap ) ) / 2 ) ) );
}
// Création tableau temporaire de la taille d'un overlap
short temp[(int)overlap];
// boucle for complete
int x = 0; //index du tab temp et début de chaque fichier k+1
int k = 0;
s = 0;
//parcours de chaque fichiers, cad chaque case du tab de struct on commence à 1.
for (k=1; k < nb_files; k++) {
//re-inititalise à 0 pour chaque boucle
x = 0;
/* On applique un "cos+1" fade-out sur les "overlap" derniers échantillons de tab_sample
* On applique un "cos+1" fade-in sur les "overlap" premiers échantillons du segment courant
* On ajoute le deux segements dans tab_sample[]
*/
for (s = nb_sample - overlap; s< nb_sample; s++) {
tab_sample[s] = (short)(tab_sample[s] * (( 1 + cos( (x * M_PI) / overlap ) ) / 2)); // cos+1 fade-out les derniers
temp[x] = file_struct[k].data.data[x]; // copie des samples dans le tableau temporaire
temp[x] = (short) (temp[x] * (1 - ( (1+cos( (x * M_PI) / overlap ) ) / 2 ) ) ); // cos+1 fade-in dans le tab temporaire
tab_sample[s] += temp[x];
x++;
}
// On commence à la position nb_sample
// et on copie du reste du fichier k jusque sa fin
for (s = overlap; s < file_struct[k].data.Subchunk2Size / file_struct[k].fmt.Blockalign; s++) {
tab_sample[nb_sample] = file_struct[k].data.data[s];
nb_sample++;
}
}
// Le fade out des "overlap" derniers echantillons est inutile car il se finit par un fichier vide.wav
Il ne nous reste plus qu’à appeler la fonction create_structure (détaillée dans la suite) pour créer une seule structure WAVE qui sera écrite dans un fichier dans la partie suivante.
// Appel à la fonction qui crée la structure
create_structure(file_struct[0], tab_sample, nb_sample, sortie);
// Libère l'espace alloué pour le tableau tab_sample
free(tab_sample);
Fonction create_structure
Cette fonction remplie une structure à partir d’une structure d’un fichier WAVE déja ouvert, elle prend ne paramètre :
- file_struct, la structure d’un fichier WAVE, il va servir de base pour remplir la nouvelle structure.
- tab_sample, le tableau contenant les données « data » du fichier WAVE à écrire.
- nbr_sample, le nombre d’échantillons présent dans tab_sample.
- output, la structure qui va être remplie avec les données des différents fichiers.
Nous allons, dans un premier temps, calculer les valeurs qui varient tels que les chunksize, sub_chunksize. (Les formules sont issues de ce site).
// subck2size correspond au nombre de bytes présent dans le block donné du fichier WAVE
dword sbck2size = nbr_sample * file_struct.fmt.Blockalign;
// sbck1size correspond au nombre de données du block fmt
dword sbck1size = 16;
// cksize correspond à la taille du fichier WAVE moins la taille de sbck1size et sbck2size
word cksize = 4 + (8 + sbck1size) + (8 + sbck2size);
Les étapes suivantes vont être assez répétitive, à chaque fois on remplie la nouvelle structure avec les valeurs de la structure témoin.
/*
* On copie les données du block RIFF en prenant les données du fichier témoin
*
* Pour ChunkSize, on le remplie par la valeur cksize calculée précedement
*/
strcpy(output->RIFF.ChunkID, file_struct.RIFF.ChunkID);
output->RIFF.ChunkSize = cksize;
strcpy(output->RIFF.Format, file_struct.RIFF.Format);
/*
* On copie les données de Subchunk1ID (contenant "fmt ") dans la structure générée
*
* On affecte les valeurs du témoin à la nouvelle structure générée
*/
strcpy(output->fmt.Subchunk1ID, file_struct.fmt.Subchunk1ID);
output->fmt.Subchunk1Size = file_struct.fmt.Subchunk1Size;
output->fmt.AudioFormat = file_struct.fmt.AudioFormat;
output->fmt.NumChannels = file_struct.fmt.NumChannels;
output->fmt.SampleRate = file_struct.fmt.SampleRate;
output->fmt.ByteRate = file_struct.fmt.ByteRate;
output->fmt.Blockalign = file_struct.fmt.Blockalign;
output->fmt.BitsPerSample = file_struct.fmt.BitsPerSample;
/*
* On copie les données de Subchunk2ID (contenant "WAVE") dans la structure générée
*
* Pour Chunk2Size, on le remplie par la valeur cksize calculée précedement
*/
strcpy(output->data.Subchunk2ID, file_struct.data.Subchunk2ID);
output->data.Subchunk2Size = sbck2size;
La dernière étape consiste à ajouter les valeurs du tableau tab_sample contenant les données (ici les échantillons sonores). Pour cela, on calcule la valeur du block data grâce à la définition de la structure WAVE, puis on recopie à l’aide d’une boucle chaque échantillons.
/*
* Alocation de la taille du tableau contenant les échantillons
* Subchunk2Size correspond au nombres de bytes du block data
* Blockalign ( = Nombres de channel * Nombre de bits par échantillons /8 ) est le nombre de bytes par échantillons (samples)
* Subchunk2Size / Blockalign correspond au nombre d'échantillons présent
*/
output->data.data = malloc((unsigned int)(output->data.Subchunk2Size / output->fmt.Blockalign) * sizeof(short*) );
// Indice pour la boucle for à venir
int i = 0;
/*
* nbr_sample correspond au nombre d'échantillons présent dans le block data
* On parcours le tableau tab_sample contenant les données du block data générés par les autres fontions
* On affecte ces valeurs dans le tableau d'échnatillons de la strcuture qui sera générées
*/
for (i=0; i < nbr_sample ; i++) { output->data.data[i] = tab_sample[i];
}
Conclusion
Il ne reste plus qu’à écrire cette structure dans un fichier afin d’avoir un fichier WAVE.
Comme d’habitude, voici le code complet :
#include
#include
#include
#include
#include "Concat.h"
#include "WAVE.h"
/*!
* file Concatetion.c
*
* brief Effectue la concaténation des différents fichiers.
*
*defgroup concat Concatenation
* Module qui charge les différents fichiers sons à l'aide de la fonction store(), puis effectue une fade.
* Enfin, il les concaténe et renvoie une srcutre de fichier WAVE.
*/
/*!
* fn void create_structure(WAVE file_struct, short *tab_sample, dword nbr_sample, WAVE *output)
*
* param file_struct Structure d'un fichier WAVE, il va servir de base pour remplir la nouvelle structure.
* param tab_sample Tableau contenant les données "data" du fichier WAVE à écrire.
* param nbr_sample Nombre d'échantillons présent dans tab_sample.
* param output Structure qui va être remplie avec les données des différents fichiers.
*
* brief Remplie une structure à partir d'une structure d'un fichier WAVE déja ouvert.
*
* ingroup concat
*/
void create_structure(WAVE file_struct, short *tab_sample, dword nbr_sample, WAVE *output) { //en entrée le tab de struct et une struct qui sera utilisé dans write_wave
// subck2size correspond au nombre de bytes présent dans le block donné du fichier WAVE
dword sbck2size = nbr_sample * file_struct.fmt.Blockalign;
// sbck1size correspond au nombre de données du block fmt
dword sbck1size = 16;
// cksize correspond à la taille du fichier WAVE moins la taille de sbck1size et sbck2size
word cksize = 4 + (8 + sbck1size) + (8 + sbck2size);
/*
* On copie les données du block RIFF en prenant les données du fichier témoin
*
* Pour ChunkSize, on le remplie par la valeur cksize calculée précedement
*/
strcpy(output->RIFF.ChunkID, file_struct.RIFF.ChunkID);
output->RIFF.ChunkSize = cksize;
strcpy(output->RIFF.Format, file_struct.RIFF.Format);
/*
* On copie les données de Subchunk1ID (contenant "fmt ") dans la structure générée
*
* On affecte les valeurs du témoin à la nouvelle structure générée
*/
strcpy(output->fmt.Subchunk1ID, file_struct.fmt.Subchunk1ID);
output->fmt.Subchunk1Size = file_struct.fmt.Subchunk1Size;
output->fmt.AudioFormat = file_struct.fmt.AudioFormat;
output->fmt.NumChannels = file_struct.fmt.NumChannels;
output->fmt.SampleRate = file_struct.fmt.SampleRate;
output->fmt.ByteRate = file_struct.fmt.ByteRate;
output->fmt.Blockalign = file_struct.fmt.Blockalign;
output->fmt.BitsPerSample = file_struct.fmt.BitsPerSample;
/*
* On copie les données de Subchunk2ID (contenant "WAVE") dans la structure générée
*
* Pour Chunk2Size, on le remplie par la valeur cksize calculée précedement
*/
strcpy(output->data.Subchunk2ID, file_struct.data.Subchunk2ID);
output->data.Subchunk2Size = sbck2size;
/*
* Alocation de la taille du tableau contenant les échantillons
* Subchunk2Size correspond au nombres de bytes du block data
* Blockalign ( = Nombres de channel * Nombre de bits par échantillons /8 ) est le nombre de bytes par échantillons (samples)
* Subchunk2Size / Blockalign correspond au nombre d'échantillons présent
*/
output->data.data = malloc((unsigned int)(output->data.Subchunk2Size / output->fmt.Blockalign) * sizeof(short*) );
// Indice pour la boucle for à venir
int i = 0;
/*
* nbr_sample correspond au nombre d'échantillons présent dans le block data
* On parcours le tableau tab_sample contenant les données du block data générés par les autres fontions
* On affecte ces valeurs dans le tableau d'échnatillons de la strcuture qui sera générées
*/
for (i=0; i < nbr_sample ; i++) {
output->data.data[i] = tab_sample[i];
}
}
/*!
* fn void store(char **path_tab, WAVE *file_struct, int nb_files)
*
* param path-tab Tableau de chaine de caractère contenant les repertoires des fichiers sons
* param file_struct Tableau de structures d'un fichier WAVE.
* param nb_files Nombre de fichiers à recupérer les données
*
* brief Extrait les données de chaques fichiers WAVE et les sotvcks dans une strcuture appropriée.
*
* ingroup concat
*/
void store(char **path_tab, WAVE *file_struct, int nb_files){
// Indice pour parcourir l'ensemble des fichiers
int i = 0;
int j = 0;
// On parcours l'ensemble des fichiers
while (i < nb_files) {
// Appel de la fonction qui lit un fichier WAVE et stock les données dans uen structure
if (Read_Wav(path_tab[i], &file_struct[j]) == 0) { //reads file @i in tab d'analyse syntax and stores it in @j of struct tab file_struct
// Incrément l'indice j
j++;
} else {
printf("nerreur: fichier %s introuvablen", path_tab[i]);
printf("nsynthèse impossible");
exit(0);
}
// Incrément l'indice i
i++;
}
}
/*!
* fn void concatenation(char **path_tab, int nb_files, WAVE *sortie)
*
* param path-tab Tableau de chaine de caractère contenant les repertoires des fichiers sons
* param nb_files Nombre de fichiers à recupérer les données et concaténés
* param sortie Structure qui va être remplie avec les données des différents fichiers.
*
* brief Fonction qui va lire et stocker les données de chaque fichier, afin de la concaténer.
*
* Cette fonction va stocker les données de chaque fichier WAVE, grâce à store(), puis va effectuer un
* cross-fade (fondu croisé) en modifiant les données de chaque echantillons. Les différents block de
* données vont être concaténés en subissant un overlap (chevauchement) sur 100 ms, puis ecris dans une strcture
* de type WAVE, grâce à create_structure(), qui servira à ecrire le fichier WAVE.
*
* ingroup concat
*/
void concatenation(char **path_tab, int nb_files, WAVE *sortie) {
// Declaration d'un tableau de strcuture où seront stocké les données.
WAVE file_struct[nb_files];
// Appel à la fonction store() pout créer/remplir un tableau de structure
store(path_tab, file_struct, nb_files);
// Definit le temps de l'overlap à 100 ms
const float t_overlap = 0.01;
// Calcul le nombre de sample sur lequel on va effectuer l'overlap à partir de la valeur eb ms (t_overlap) sur laquel on veut overlaper
const float overlap = t_overlap * file_struct[0].fmt.SampleRate; //overlap time in samples
// Taille du tableau pour stocker samples
dword totalsize = 0;
int i;
/*
* Calcul de totalsize : on additione tous les nombres d'échantillons de chaque fichier
* sans prendre en compte l'overlap, afin d'être sûr de ne pas avoir un tableau trop petit
*/
for (i=0; i< nb_files; i++) {
totalsize += file_struct[i].data.Subchunk2Size / file_struct[i].fmt.Blockalign;
}
// Création du tableau pour stocker l'ensemble des échantillons
short* tab_sample;
tab_sample = malloc (totalsize * sizeof(short));
// Nombre d'échantillons dans le tableau final
unsigned int nb_sample = 0;
/*
* Copie l'intégralité des echantillons du premier fichier tab_struct[0],
* On parcourt le tableau de 0 jusqu'à la taille totale du nombre d'échantillons (nombre total de samples)
*/
for (nb_sample = 0; nb_sample < file_struct[0].data.Subchunk2Size / file_struct[0].fmt.Blockalign; nb_sample++) {
tab_sample[nb_sample] = file_struct[0].data.data[nb_sample];
}
// s: sample
int s = 0;
// On effectue un fade-in sur les premiers échantillons du premier fichier.
for (s = 0; s < overlap; s++) {
tab_sample[s] = (short) (tab_sample[s] * (1 - ( (1+cos( (s * M_PI) / overlap ) ) / 2 ) ) );
}
// Création tableau temporaire de la taille d'un overlap
short temp[(int)overlap];
// boucle for complete
int x = 0; //index du tab temp et début de chaque fichier k+1
int k = 0;
s = 0;
//parcours de chaque fichiers, cad chaque case du tab de struct on commence à 1.
for (k=1; k < nb_files; k++) {
//re-inititalise à 0 pour chaque boucle
x = 0;
/* On applique un "cos+1" fade-out sur les "overlap" derniers échantillons de tab_sample
* On applique un "cos+1" fade-in sur les "overlap" premiers échantillons du segment courant
* On ajoute le deux segements dans tab_sample[]
*/
for (s = nb_sample - overlap; s< nb_sample; s++) {
tab_sample[s] = (short)(tab_sample[s] * (( 1 + cos( (x * M_PI) / overlap ) ) / 2)); // cos+1 fade-out les derniers
temp[x] = file_struct[k].data.data[x]; // copie des samples dans le tableau temporaire
temp[x] = (short) (temp[x] * (1 - ( (1+cos( (x * M_PI) / overlap ) ) / 2 ) ) ); // cos+1 fade-in dans le tab temporaire
tab_sample[s] += temp[x];
x++;
}
// On commence à la position nb_sample
// et on copie du reste du fichier k jusque sa fin
for (s = overlap; s < file_struct[k].data.Subchunk2Size / file_struct[k].fmt.Blockalign; s++) {
tab_sample[nb_sample] = file_struct[k].data.data[s];
nb_sample++;
}
}
// Le fade out des "overlap" derniers echantillons est inutile car il se finit par un fichier vide.wav
// Appel à la fonction qui crée la structure
create_structure(file_struct[0], tab_sample, nb_sample, sortie);
// Libère l'espace alloué pour le tableau tab_sample
free(tab_sample);
}
Sur le même sujet :