Partage
  • Partager sur Facebook
  • Partager sur Twitter

Le format WAVE

Comment l'exploiter !

26 novembre 2009 à 23:11:37

Salut à tous :)

Suite au post de Lunaro concernant les fichiers audio j'ai décidé de creuser un peu le sujet pour vous expliquer comment l'exploiter :)
Je vais vous montrer comment manipuler un fichier WAVE avec un format PCM standard.

On va présenter un peu la chose :

Qu'est ce que le WAVE ?


Le fichier WAVE est un format de fichier son non compressé.
Ce fichier comprend un «header» (en-tête) de 44 octets. Ce header contient les informations relatives au fichier, notamment la fréquence d'échantillonnage, le format (PCM dans notre cas), le nombre de canaux, etc..
Ensuite viennent les données. Celles-cis sont «arrangées» en fonction des informations données dans le header.

Bon je vais pas m'étaler pour le reste ya notre ami commun à tous : Google
Tapez simplement "format wave" et vous devriez trouver plein de choses :)

Donc notre header est composé :

- Le mot "RIFF" -> 4 octets
- La taille du fichier auquel tu enlève 8. Pourquoi -8 ? Bah parce que tu enlèves tout simplement la taille de RIFF et ces 4 octets de taille -> 4 octets
- Le mot "WAVE" -> 4 octets
- Le mot "fmt " (notez qu'il y a un espace) -> 4 octets
- La taille du header restant (tout ce qui suit jusqu'à "data") 16 pour le format PCM -> 4 octets
- Le format audio (1 pour PCM, et autre chose pour ... autre chose j'ai pas trouvé plus d'infos ) -> 2 octets
- Le nombre de canaux (1 : Mono, 2 : Stereo, 3 : Stereo + centre, 4 : Avant droite et gauche, arrière droite et gauche, 5 : Surrond (3 avant, 2 arrières), 6 : Droite et gauche, centre droite et gauche, 2 arrière) -> 2 octets
- La fréquence d'échantillonnage (44100, et d'autres ) -> 4 octets
- Le "ByteRate" qui se calcule : Fréquence d'échantillonnage * nombre de canaux * Bits par échantillon / 8 -> 4 octets
- L'alignement, qui correspond au nombre d'octets pour 1 échantillon. Calcul : Nombre de canaux * Bits par échantillon / 8 -> 2 octets
- Le nombre de bits par échantillon. 8, 16, ou 24 -> 2 octets

- Eventuellement des paramètres optionnels si ce n'est pas du PCM (j'ai pas réussi à avoir beaucoup d'infos là-dessus :x )

- Le mot "data" -> 4 octets
- La taille des données. Calcul : Nombre de canaux * nombre d'échantillons * bits par échantillons / 8 -> 4 octets

- Les données :)

(copier/coller de l'autre sujet :-° )

Donc avec ce qu'on a là on peut avoir une structure de ce type :

struct wave {
	/* Le mot "RIFF" */
	char riff[4];
	/* La taille du fichier - 8
	 * Ou subTaille2 + 44 - 8 */
	int32_t taille;
	/* Le mot "WAVE" */
	char wave[4];
	
	/* Le mot "fmt " */
	char fmt[4];
	/* Taille du header jusqu'à "data" */
	int32_t subTaille1;
	/* Format du fichier */
	int16_t formatAudio;
	/* Nombres de canaux */
	int16_t nombreCanaux;
	/* Fréquence d'échantillonnage */
	int32_t freqEch;
	/* ByteRate
	 * Nombre d'ocets par seconde
	 ** Calcul :
	 *===> freqEch * nombreCanaux * bitsParEch / 8 */
	int32_t ByteRate;
	/* Alignement
	 * Nombre d'octets par échantillon
	 * Englobe tous les canaux !
	 ** Calcul :
	 *===> nombreCanaux * bitsParEch / 8 */
	int16_t align;
	/* Bits par échantillon */
	int16_t bitsParEch;
	
	/* Le mot "data" et la 
	 * taille des données */
	char Ndata[4];
	/* Taille des données */
	int32_t subTaille2;
	
	/* A allouer dynamiquement.
	 * Contiendra les données */
	void *data;
};

Petite info : Les types int16_t et int32_t sont déclarés dans <stdint.h> et permettent de garantir la taille de ce type quelque soit la plateforme. Un int16_t fera 16 bits, donc 2 octets ;)

A partir de cette structure on peut stocker toutes nos données (Yahooo!).
Ok, mais comment un signal analogique peut être converti en numérique ?
Hé bien c'est tout simplement en fonction des données que nous avons :)
La donnée la plus importante est la fréquence d'échantillonnage qui permettra de découper notre signal correctement.
On va découper notre signal à la fréquence d'échantillonnage. Comme c'est bien foutu, si je prend 44100hz (41,1Khz) j'aurais 44100 points pour une seconde :-°
Au final on va découper notre signal comme suis :
Le signal est en rouge, et les lignes bleues sont les échantillons. A chaque intersection entre la ligne bleue et la courbe on «relève» la valeur de cet échantillon :)
(J'ai pris la fonction sinus parce que c'est celle qui crée une sinusoïde pure (d'où son nom sinusoïde :-° ))

Image utilisateur
Comme on le vois, on ne relève que certains points de cette courbe. Alors qu'est-ce qu'on fait entre 2 points quand on reconstitue le signal ?
Hé bien ça nous fera tout simplement un escalier (j'ai pas eu le courage de faire la courbe :lol: je la ferai si je trouve le courage :lol: ).
Quelque chose comme ça :

.
    _
  _| |_
_|     |_       _
         |_   _|
           |_|

Venons en au fait !
Comment sont stockées ces données ?

En fait ça dépend du nombre de bits par échantillon que vous avez choisi ^^
Si vous avez choisi 8 bits, les données seront non signées et pourront prendre des valeurs allant de 0 à 255 (inclus). Le «point milieu» est situé à la valeur 127.
Par contre si vous avez choisi 16 bits, les données seront signées et pourront prendre des valeurs allant de -32768 à 32767. Le «point milieu» est situé à la valeur ... ... 0 :D
Pour 24 et 32 je suppose que c'est codé comme sur 16 bits, mais je n'ai trouvé aucune info là-dessus :-°

Le véritable inconvénient de cette pratique est pour convertir le 8 bits en 16 bits.
Pour convertir du 8 en 16 bits il faut signer la courbe et multiplier le tout par 255.
Pour convertir du 16 bits en 8 il faut faire l'inverse :lol:
(j'expliquerai sûrement avec un exemple ou deux sur de prochains posts)

Bon bah voilà !!! C'est fini ?
Hé bien non :p
Maintenant que nous avons nos échantillons il va falloir les organiser dans notre fichier ;)

Si nous sommes codés sur 16 bits et que nous sommes en MONO (1 seule enceinte) il n'y a pas vraiment de problèmes. Il suffit d'aligner les données ^^
Si nous sommes codés sur 16 bits et que nous sommes en STEREO (2 enceintes) les données seront arrangées de la manière suivante :
| Gauche Droite | | Gauche Droite | | Gauche Droite | | Gauche Droite | etc...
(les '|' ne sont là que pour bien montrer que les données sont stockées par groupe de 2)
Avec ça on peut stocker nos 2 courbes (hé oui il y a 2 courbes, une pour l'enceinte droite et une autre pour l'enceinte gauche). On pourrait avoir quelque chose comme ça (les valeurs sont des valeurs prises au hasard) :

| FFAB 1234 | | 5489 A8D5 | | 6831 234C | etc..

Dans cet exemple les valeurs FFAB, 5489, 6831 correspondent a l'enceinte gauche, et les autres valeurs à l'enceinte droite (encore une fois les '|' ne sont que pour séparer les valeurs et bien les mettre en évidence, ils n'apparaissent pas dans le fichier final ! )

Le code final pour générer un beau son serait de cette forme :
(J'ai essayé de le commenter un maximum pour que vous ne soyez pas perdus avec ce que j'ai mis plus haut)


/*
 *  main.c
 *  Gestion des fichiers WAVE
 *
 *  Created by Pouet on 26/11/09.
 *  
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

/* Pour forcer l'alignement mémoire.
 * Normalement yen a pas besoin, mais au cas où ...
 * (merci Fvirtman  ) */
#pragma pack(push, 1)

/* Nombre de secondes pour notre fichier */
#define N_SEC 10

/* Formats audios
 * (je ne connais que le PCM ...) */
enum F_AUDIO {
	PCM = 1
};

/* Taille de l'en-tête subTaille1 */
enum T_AUDIO {
	T_PCM = 16
};

/* Canaux :
 *	Mono = 1 seule enceinte
 *	Stéréo = 2 enceintes
 *	GDC = 3 enceintes (gauche, droite, centre)
 *	GD_GD = 4 enceintes (avant gauche et droite, arrière gauche et droite)
 *	GDCS = 5 enceintes (GDC + Surround)
 *	All = 6 enceintes (GDC, centre gauche, centre droit, surround) */
enum CAN {
	MONO = 1,
	STEREO = 2,
	GDC = 3,
	GD_GD = 4,
	GDCS = 5,
	ALL = 6
};

/* Fréquence d'échantillonnage
 * Valeurs en hz*/
enum F_ECH {
	F_8K = 8000,
	F_11K = 11025,
	F_22K = 22050,
	F_41K = 44100,
	F_48K = 48000, /* Encodage d'un CD audio  */
	F_96K = 96000
};

/* Bits par échantillon */
enum BPE {
	BPE_8 = 8,
	BPE_16 = 16,
	BPE_24 = 24,
	BPE_32 = 32
};

#define T_ENTETE 44

/* L'en-tête fait 44 octets.
 * Il ne faut pas oublier d'enlever sizeof(void*) si
 * on utilise sizeof sur notre structure. */
struct wave {
	/* Le mot "RIFF" */
	char riff[4];
	/* La taille du fichier - 8
	 * Ou subTaille2 + 44 - 8 */
	int32_t taille;
	/* Le mot "WAVE" */
	char wave[4];
	
	/* Le mot "fmt " */
	char fmt[4];
	/* Taille du header jusqu'à "data" */
	int32_t subTaille1;
	/* Format du fichier */
	int16_t formatAudio;
	/* Nombres de canaux */
	int16_t nombreCanaux;
	/* Fréquence d'échantillonnage */
	int32_t freqEch;
	/* ByteRate
	 * Nombre d'ocets par seconde
	 ** Calcul :
	 *===> freqEch * nombreCanaux * bitsParEch / 8 */
	int32_t ByteRate;
	/* Alignement
	 * Nombre d'octets par échantillon
	 * Englobe tous les canaux !
	 ** Calcul :
	 *===> nombreCanaux * bitsParEch / 8 */
	int16_t align;
	/* Bits par échantillon */
	int16_t bitsParEch;
	
	/* Le mot "data" et la 
	 * taille des données */
	char Ndata[4];
	/* Taille des données */
	int32_t subTaille2;
	
	/* A allouer dynamiquement.
	 * Contiendra les données */
	void *data;
};

void initWave(struct wave *wav) {
	/* Les constantes symboliques */
	strncpy(wav->riff, "RIFF", 4);
	strncpy(wav->wave, "WAVE", 4);
	strncpy(wav->fmt, "fmt ", 4);
	strncpy(wav->Ndata, "data", 4);
	
	//wav->taille = wav->subTaille2 = 0;
	
	wav->subTaille1 = T_PCM;
	wav->formatAudio = PCM;
	wav->nombreCanaux = STEREO;
	wav->freqEch = F_41K;
	wav->bitsParEch = BPE_16;
	wav->ByteRate = wav->freqEch * wav->nombreCanaux * (wav->bitsParEch / 8);
	wav->align = wav->nombreCanaux * (wav->bitsParEch / 8);
	
	/* On utilise la constante N_SEC pour créer notre fichier */
	/* On aura un fichier de N_SEC secondes */
	/* On peut calculer subTaille2 en fonction du byterate. Ce qui nous donne : */
	wav->subTaille2 = wav->ByteRate * N_SEC;
	
	/* On calcule la taille du fichier */
	wav->taille = wav->subTaille2 + 44 - 8;
	
	/* On alloue la partie données */
	wav->data = malloc(wav->subTaille2);
}

void genererPiste(struct wave *wav) {
	int16_t *data = wav->data;
	int cpt;
	double val;
	
	/* On crée une sinusoïde pour créer un jooooli son  */
	for (cpt = 0, val = 0.0; cpt < wav->subTaille2/2; cpt += 2, val += 0.0284951714612 /*0.0142475857306*/) {
		/* On met la même valeur pour les 2 enceintes */
		/* Essayez d'en mettre un à 0 et de laisser l'autre 
		 * Vous aurez un bruit que sur une seule enceinte  */
		data[cpt] = sin(val) * 32267;
		data[cpt+1] = sin(val) * 32267;
	}
}

int main(void) {
	struct wave wav = { 0 };
	FILE *fich = fopen("test.wav", "wb+");
	
	if (fich == NULL)
		return EXIT_FAILURE;
	
	/* Seul les paramètres taille, subTaille2 et data ne sont pas initialisés */
	initWave(&wav);
	/* On génère des données */
	genererPiste(&wav);
	/* On écris l'en-tête SANS LE wav->data */
	fwrite(&wav, T_ENTETE, 1, fich);
	/* On écris les data */
	fwrite(wav.data, wav.subTaille2, 1, fich);
	
	/* On libère notre mémoire, et on ferme le fichier */
	free(wav.data);
	fclose(fich);
	/* Voilà notre fichier son est créé  */
	return EXIT_SUCCESS;
}


Hé voilà vous avez généré un beauuuuu son (strident certes) mais vous l'avez fait !!!
Vous pouvez aussi essayer de faire le code de vos propres mains :)
On pourra noter que la taille du fichier de sortie fait 1 764 044 octets (≈1,8Mo) ce qui est assez énorme pour un fichier de 10 secondes ...
De ce fait on comprend mieux pourquoi la compression à fait des ravages :)


Maintenant qu'on vient de créer notre son (on est content ouais !!!) il faudrait émettre un son audible :p
On va faire simple : La gamme.
Chaque note de musique a une fréquence. Vous pouvez retrouver la liste ici : http://musiweb.free.fr/tableaux/tablea [...] tes_hertz.htm
Donc d'après cette fréquence on va pouvoir créer notre son :)
Quelle forme à ce son ?
Hé bien la même forme que la courbe donnée ci-dessus :)
Cette sinusoïde pure est un son pur. De ce fait il est moche ...
Hé ben oui !
Ce qui fait qu'un instrument de musique joue la note LA mais que chaque instrument ne rend pas le même son (mais pourtant la même note !) vient du fait qu'il y a des harmoniques :)
Ce sont ces harmoniques qui forment le son et le rendent propre à chaque instrument.
Les harmoniques sont des répliques du fondamental (la courbe principale) qui ont une fréquence 2, 3, 4, etc.. plus élevée. Ce ne sont que des multiples entiers.
Dans cet exemple le fondamental est <math>\(sin(x)\)</math> et on a l'harmonique de rang 2 <math>\(sin(2x)\)</math>. Normalement les amplitudes des harmoniques sont elles-aussi diminuées.

Image utilisateur


Bon c'est bien beau tout ça, mais moi je veux créer ma musique !

Il nous faut plusieurs informations pour créer ça :)
Déjà, la fréquence d'échantillonnage qui nous permettra de savoir à quelle vitesse découper notre son. On prendra 44100. Ensuite il nous faut ... notre fonction ! Dans notre cas on prendra le plus simple. La sinusoïde pure <math>\(sin(x)\)</math>. Il faut aussi savoir sur combien de bits est codé notre signal. On prendra 16 bits. Ensuite il nous faut le nombre de canaux. 2 dans notre cas. Et la durée du son. Pour faire plus simple on prendra des valeurs entières.

La gamme est composée de 7 notes : DO, RE, MI, FA, SOL, LA SI. On jouera les notes de DO à DO + 1 octave et ensuite de DO + 1 octave à DO. Ce qui nous donne comme séquence : DO, RE, MI, FA, SOL, LA, SI, DO_1, SI, LA, SOL, FA, MI, RE, DO.
Avec le lien que je vous ai donné plus haut on a toutes les fréquences de notre gamme :)

Maintenant comment faire pour convertir tout ça ?

Comme on prend des valeurs entières pour le temps on simplifie la chose. On va découper notre intervalle de temps en 44100.
On va le découper de manière intelligente ;)
Une période pour la fonction sinus est 2*PI (≈6,28).
On va donc découper notre fonction comme ça : <math>\(\frac{2\pi }{44100}\)</math>

Pour une fréquence de 1hz on a une seule sinusoïde dans 1 seconde.
Pour une fréquence de 2hz on a 2 sinusoïdes, 3hz on a 3 sinusoïdes, etc...
Au final on a X sinusoïdes dans 1 seconde.
En fait X correspond tout simplement à la fréquence ;)
Donc pour avoir la valeur qui nous intéresse il faut faire : <math>\(\frac{2\pi }{44100}*X\)</math>

Par exemple pour le LA à 440hz on aura : <math>\(\frac{2\pi }{44100}*440 \approx 0.062689377214490\)</math>
Pour faire notre gamme on prendra 1 note = 1 seconde. Sachant qu'on doit jouer 15 notes, notre piste fera 15 secondes ;)
On peut calculer toutes les valeurs dans le programme, moi je l'ai fait à la main et mis en dur dans le programme :p

Maintenant qu'on a toutes les infos nécessaires on va pouvoir créer notre programme :lol:
Voilà la fonction :
Note : La fonction est assez redondante, on peut sûrement l'améliorer mais de cette manière-là c'est plus parlant.

void genererPiste(struct wave *wav) {
	int taille = wav->subTaille2/(2*N_SEC);
	int16_t *data = wav->data;
	int cpt;
	double val;
	/* 2 * PI / 44100 = 0,000142475857306
	 * DO		261.625 hz	0.037275246167682
	 * DO#		277.182 hz	0.039491743079697
	 * RE		293.664 hz	0.041840030159809
	 * RE#		311.126 hz	0.044327943580081
	 * MI		329.627 hz	0.046963889416093
	 * FA		349.228 hz	0.049756558695141
	 * FA#		369.994 hz	0.052715212347950
	 * SOL		391.995 hz	0.055849823684532
	 * SOL#		415.304 hz	0.059170793442470
	 * LA		440.000 hz	0.062689377214490
	 * LA#		466.163 hz	0.066416973069178
	 * SI		493.883 hz	0.070366403833691
	 * DO_1		523.251 hz	0.074550634811044
	 */
#define DO		0.037275246167682
#define RE		0.041840030159809
#define MI		0.046963889416093
#define FA		0.049756558695141
#define SOL		0.055849823684532
#define LA		0.062689377214490
#define SI		0.070366403833691
#define DO_1	0.074550634811044
	
#define CREER_NOTE(cpt, val, taille, inc, data) \
	for (cpt = 0, val = 0.0; cpt < taille; cpt += 2, val += inc) {	\
		data[cpt] = sin(val) * 32267;	\
		data[cpt+1] = sin(val) * 32267;	\
	}
	
	CREER_NOTE(cpt, val, taille, DO, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, RE, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, MI, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, FA, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, SOL, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, LA, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, SI, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, DO_1, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, SI, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, LA, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, SOL, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, FA, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, MI, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, RE, data)
	data += taille;
	CREER_NOTE(cpt, val, taille, DO, data)
}

Et nous voilà avec un super fichier WAVE qui joue la gamme de musique :D
On notera encore la taille du fichier qui est assez lourde ... 2 646 044 octets (≈2,7 Mo)

Voilà la deuxième version de la gamme : Deuxième version.
Elle prend en compte l'endianess et quelques améliorations :)

Bon ben ce petit post/tutoriel touche à sa fin :'(
Si vous avez des questions n'hésitez pas j'essayerai d'y répondre au mieux :)
Je vais sûrement par la suite rajouter des courbes, et quelques exemples mais bon ça dépendra du nombre de personnes intéressées :)

@+ tout le monde

Edit : Rajout de la création d'une gamme et explications.
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 16:10:45

salut,

super post, je pense que je vais essayer de faire un truc pour jouer de la musique, ou pour générer des sons voire des musiques (dire que je suis nul en musique).

en tout cas je suis interressé.

Hedi
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 16:18:01

Le code de la gamme compile mais ne marche pas (gros bug)
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 16:52:10

Tu peux décrire le problème plus en détail stp ? :)
Ya un truc dont je n'ai pas parlé aussi, c'est peut-être dû à ça ^^ (et quasiment sûr !)
Les données doivent être obligatoirement stockées en LITTLE ENDIAN. Si ce n'est pas le cas tu n'auras pas le résultat souhaité :)
Par exemple si j'ai une valeur de 5469, on aura en héxa 0x155D. Les données devrons être stockées de cette manière là : 5D 15
Si c'est l'inverse tu auras des valeurs incorrectes :)

Je vais essayer de faire une fonction pour ça ;)
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 16:58:33

je crois que les fonction htons et ntohs le font (elles passent de host/network vers network/host ce qui doit revenir à passer de big/little endian à little/big endian je crois)

Hedi
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 17:13:30

Joli topic :)

Le code en revanche, ne compile pas avec visual, qui ne supporte pas le C99.
stdint.h n'existant pas, je me rabat sur:
typedef short int16_t;
typedef int int32_t;


Après ça se corse, j'ai 30 warnings lors de l'affectation à data à partir d'un double (pas gênant je pense), mais ça plante à l'execution sur cette ligne:
CREER_NOTE(cpt, val, taille, SOL, data)
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 17:44:11

@ hedi07 : Ok je vais essayer de voir :)

@ [ZBM] : Oui pour les warning tu peux faire un cast :)

data[cpt] = (int16_t) (sin(val) * 32267);
data[cpt+1] = (int16_t) (sin(val) * 32267);

Sinon c'est bizarre que ça plante :euh:
J'ai beau lire et relire j'arrive pas à voir d'où ça vient :euh:
Tu peux me fournir plus d'infos stp ? :)

Oui sinon pour le int16_t je savais pas que c'était uniquement en C99 :)

Par contre il y a une chose à laquelle je n'ai pas fait attention :euh:
Je considère qu'à la fin de ma boucle je passe à la note suivante, mais je ne tiens pas compte de l'état de la note précédente et du coup au moment du passage entre 2 notes on peut entendre un petit grésillon.
C'est dû au fait que la sinusoïde n'est pas continue et du coup c'est pas terrible :x

Image utilisateur

On vois bien sur l'image que la sinusoïde de gauche n'a pas la même fréquence que celle de droite ;)

Edit : Petite erreur sur le cast.
  • Partager sur Facebook
  • Partager sur Twitter
27 novembre 2009 à 18:03:33

Bah ça segfault, mais comme c'est dans une macro, c'est pas évident à debugger.

Citation : Debugger Visual

wav->data 0x00580040
wav->taille 1764036

Violation d'accès lors de l'écriture à l'emplacement 0x0072f000.




edit: ça segfault lors du développement de CREER_NOTE() sur le deuxième SOL, avec cpt = 656, sur cette ligne:
data[cpt] = sin(val) * 32267;

  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 13:21:04

Au lieu d'essayer vainemment de réinventer la roue, regarde plutot les exemples de MSDN (en C) sur les WAV, MP3, WMA, etc..
(premiers exemples de 1990...)
Car tu n'as pas l'air d'avoir tout saisi :-)
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 15:52:10

Ton image n'a aucun rapport ou alors je ne comprend pas
  • Partager sur Facebook
  • Partager sur Twitter
Anonyme
28 novembre 2009 à 16:07:36

en faisant çà tu t'exposes à tomber des sur des waves qui peuvent faire planter ton code!

Pourquoi? Parceque tu n'as pas compris ce qu'était le RIFF! Il s'agit en fait de quelque chose de plus général que les .wav, il s'agit d'une façon de sérialiser des données en chunks.

Du coup tu pourrais très bien avoir entre le chunk WAVE qui décrit le format et chunk DATA un chunk mis par une application random genre INFO avec des infos sur qui à créer le son, ou alors un chunk LSYN contenant des informations de lip sync, ...

(j'ai inventer ces 2 chunks, mais on saisi l'idée)
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 16:12:06

Son code ne fait qu'écrire un fichier wave galopin_, il ne les lit pas.
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 16:21:31

Citation : Pouet_forever

marcus5 == georges135
http://www.siteduzero.com/forum-83-449 [...] html#r4248027


Ok, il est pénible... Mais il n'a pas toujours tout à fait tort. ;)
En même temps si ce n'était pas un guignol, il donnerait un lien, ou il montrerait la bonne utilisation, ou il expliquerait... :)
Là il ne sert à rien.
  • Partager sur Facebook
  • Partager sur Twitter
Zeste de Savoir, le site qui en a dans le citron !
28 novembre 2009 à 16:24:27

Oui j'ai juste expliqué comment fonctionnais le fichier wave.
Pour mon exemple j'ai juste créé un fichier pour montrer comment créer un fichier et pas pour le lire ;)
Pour le lire c'est vrai qu'il faut prendre plus de choses en compte :)
Je vais mettre une autre version de ma gamme en tenant compte des décalages dus à la taille des notes (la dernière image que j'ai mise). De plus j'ai fait une fonction plutôt qu'une macro ^^
J'essaye de me pencher sur l'endianess mais comme je n'ai pas de PC pour tester le big c'est compliqué ^^
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 16:50:52

@marcus5: Le code de Pouet_forever n'est peut-être pas le plus professionnel qui soit, mais il a l'avantage de montrer les éléments importants du format WAVE. Si on passe notre temps à utiliser les trucs déjà fait, comment on fait pour les améliorer un jour puisqu'on en connaît pas le fonctionnement? Après, il est clair que le jour où on a pigé le truc, il vaut mieux passer à ce qui existe déjà, pour gagner du temps et être plus efficace.
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 22:26:28

Voilà la nouvelle version de la gamme qui (normalement) gère l'endianess et règle les distorsions du son entre 2 notes :)
Voilà le programme complet :

/*
 *  main.c
 *  Gestion des fichiers WAVE
 *
 *  Created by Pouet on 26/11/09.
 *  Dernière modification 29/11/09.
 *  
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef _MSC_VER
#include <windows.h>
#define _USE_MATH_DEFINES
typedef INT16 int16_t;
typedef INT32 int32_t;
#else
#include <stdint.h>
#endif

#include <math.h>

/* Pour forcer l'alignement mémoire.
 * Normalement yen a pas besoin, mais au cas où ...
 * (merci Fvirtman  ) */
#pragma pack(push, 1)

/* Nombre de secondes pour notre fichier */
#define N_SEC 15

#define PI		3.14159265358979323846264338327950288

/* Fréquence des notes en hertz */
#define F_DO	261.625
#define F_RE	293.664
#define F_MI	329.627
#define F_FA	349.228
#define F_SOL	391.995
#define F_LA	440.000
#define F_SI	493.883
#define F_DO_1	523.251

/* Calcul de l'intervalle pour 1 hz */
#define INTERVALLE (2 * PI / F_41K)
/* On multiplie l'intervalle par notre fréquence
 * pour trouver l'intervalle de la note */
#define DO		(INTERVALLE * F_DO)
#define RE		(INTERVALLE * F_RE)
#define MI		(INTERVALLE * F_MI)
#define FA		(INTERVALLE * F_FA)
#define SOL		(INTERVALLE * F_SOL)
#define LA		(INTERVALLE * F_LA)
#define SI		(INTERVALLE * F_SI)
#define DO_1	(INTERVALLE * F_DO_1)

/* Formats audios
 * (je ne connais que le PCM ...) */
enum F_AUDIO {
	PCM = 1
};

/* Taille de l'en-tête subTaille1 */
enum T_AUDIO {
	T_PCM = 16
};

/* Canaux :
 *	Mono = 1 seule enceinte
 *	Stéréo = 2 enceintes
 *	GDC = 3 enceintes (gauche, droite, centre)
 *	GD_GD = 4 enceintes (avant gauche et droite, arrière gauche et droite)
 *	GDCS = 5 enceintes (GDC + Surround)
 *	All = 6 enceintes (GDC, centre gauche, centre droit, surround) */
enum CAN {
	MONO = 1,
	STEREO = 2,
	GDC = 3,
	GD_GD = 4,
	GDCS = 5,
	ALL = 6
};

/* Fréquence d'échantillonnage
 * Valeurs en hz*/
enum F_ECH {
	F_8K = 8000,
	F_11K = 11025,
	F_22K = 22050,
	F_41K = 44100,
	F_48K = 48000, /* Encodage d'un CD audio  */
	F_96K = 96000
};

/* Bits par échantillon */
enum BPE {
	BPE_8 = 8,
	BPE_16 = 16,
	BPE_24 = 24,
	BPE_32 = 32
};

#define T_ENTETE 44

/* L'en-tête fait 44 octets.
 * Il ne faut pas oublier d'enlever sizeof(void*) si
 * on utilise sizeof sur notre structure. */
struct wave {
	/* Le mot "RIFF" */
	char riff[4];
	/* La taille du fichier - 8
	 * Ou subTaille2 + 44 - 8 */
	int32_t taille;
	/* Le mot "WAVE" */
	char wave[4];
	
	/* Le mot "fmt " */
	char fmt[4];
	/* Taille du header jusqu'à "data" */
	int32_t subTaille1;
	/* Format du fichier */
	int16_t formatAudio;
	/* Nombres de canaux */
	int16_t nombreCanaux;
	/* Fréquence d'échantillonnage */
	int32_t freqEch;
	/* ByteRate
	 * Nombre d'ocets par seconde
	 ** Calcul :
	 *===> freqEch * nombreCanaux * bitsParEch / 8 */
	int32_t ByteRate;
	/* Alignement
	 * Nombre d'octets par échantillon
	 * Englobe tous les canaux !
	 ** Calcul :
	 *===> nombreCanaux * bitsParEch / 8 */
	int16_t align;
	/* Bits par échantillon */
	int16_t bitsParEch;
	
	/* Le mot "data" et la 
	 * taille des données */
	char Ndata[4];
	/* Taille des données */
	int32_t subTaille2;
	
	/* A allouer dynamiquement.
	 * Contiendra les données */
	void *data;
};

void initWave(struct wave *wav) {
	/* Les constantes symboliques */
	strncpy(wav->riff, "RIFF", 4);
	strncpy(wav->wave, "WAVE", 4);
	strncpy(wav->fmt, "fmt ", 4);
	strncpy(wav->Ndata, "data", 4);
	
	wav->subTaille1 = T_PCM;
	wav->formatAudio = PCM;
	wav->nombreCanaux = STEREO;
	wav->freqEch = F_41K;
	wav->bitsParEch = BPE_16;
	wav->ByteRate = wav->freqEch * wav->nombreCanaux * (wav->bitsParEch / 8);
	wav->align = wav->nombreCanaux * (wav->bitsParEch / 8);
	
	/* On utilise la constante N_SEC pour créer notre fichier */
	/* On aura un fichier de N_SEC secondes */
	/* On peut calculer subTaille2 en fonction du byterate. Ce qui nous donne : */
	wav->subTaille2 = wav->ByteRate * N_SEC;
	
	/* On calcule la taille du fichier */
	wav->taille = wav->subTaille2 + 44 - 8;
	
	/* On alloue la partie données */
	wav->data = calloc(wav->subTaille2, sizeof(char));
}

int16_t convertirEndian(int16_t n) {
	long e = 1;
	char *p = (char *) &e;
	int i;
	union inverse {
		int16_t n;
		char t[2];
	};
	union inverse inv = { 0 };
	/* Little endian */
	if (*p == 1)
		return n;
	/* Big endian */
	p = (char*) &n;
	for (i = 0; i < 2; i++, p++)
		inv.t[1-i] = *p;
	return inv.n;
}

void creerNote(int taille, double inc, int16_t *data) {
	int cpt;
	double val;
	
	for (cpt = 0, val = 0.0; cpt < taille; cpt += 2, val += inc) {
		data[cpt] = convertirEndian(sin(val) * 32267);
		data[cpt+1] = convertirEndian(sin(val) * 32267);
	}
}

void genererPiste(struct wave *wav) {
	double taille = wav->subTaille2 / (wav->nombreCanaux*N_SEC);
	int16_t *data = wav->data;
	int tmp;
	int i;
	/* 2 tableaux de correspondance pour pouvoir faire la boucle */
	double freq[N_SEC] = {
		F_DO, F_RE, F_MI, F_FA, F_SOL, F_LA, F_SI, F_DO_1,
		F_SI, F_LA, F_SOL, F_FA, F_MI, F_RE, F_DO };
	double inter[N_SEC] = {
		DO, RE, MI, FA, SOL, LA, SI, DO_1,
		SI, LA, SOL, FA, MI, RE, DO };
	
	for (i = 0; i < N_SEC; i++) {
		tmp = (int) (taille - fmod(taille, F_41K / freq[i]) - (F_41K / freq[i]));
		creerNote(tmp, inter[i], data);
		data += tmp;
	}
}

int main(void) {
	struct wave wav = { 0 };
	FILE *fich = fopen("test.wav", "wb+");
	
	if (fich == NULL)
		return EXIT_FAILURE;
	
	/* Seul les paramètres taille, subTaille2 et data ne sont pas initialisés */
	initWave(&wav);
	if (wav.data == NULL)
		return 1;
	/* On génère des données */
	genererPiste(&wav);
	/* On écris l'en-tête SANS LE wav->data */
	fwrite(&wav, T_ENTETE, 1, fich);
	/* On écris les data */
	fwrite(wav.data, wav.subTaille2, 1, fich);
	
	/* On libère notre mémoire, et on ferme le fichier */
	free(wav.data);
	fclose(fich);
	/* Voilà notre fichier son est créé  */
	return EXIT_SUCCESS;
}

Il reste encore quelques distorsions mais je n'arrive pas à régler ce problème :euh:
(et dites moi si mon code compile sous VS parce qu'apparemment ya des trucs qu'il aime pas >_< )
Au niveau des distorsions sur certaines notes j'ai un décalage d'une demi période :

Image utilisateur

Edit : Modification du code suite aux remarques de [ZBM] pour compiler sous VS :)
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 22:54:19

Je te propose d'utiliser une fonction fréquence pour obtenir la fréquence d'une note, ce qui évite les multiples defines :
double frequence(int note) //note est un entier; 0 représente le LA 440, 1 le la# au-dessus, 12 le la du dessus, -12 celui du dessous, etc...
{
	return 440.0*pow(2.0,note/12.0);
}
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 23:05:50

Je savais bien que il y avais une fonction pour ça :)
Merci :)
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 23:10:11

D'ailleurs ça nous permet de monter la gamme continûment (c'est assez amusant !). Ici un demi-ton par seconde
double frequence(double note) //note est un entier; 0 représente le LA 440, 1 le la# au-dessus, 12 le la du dessus, -12 celui du dessous, etc...
{
	return 440.0*pow(2.0,note/12.0);
}
void genererPiste(struct wave *wav) {
	int16_t *data = wav->data;
	int cpt;
	double i = 0.0; 
	
	
	/* On crée une sinusoïde pour créer un jooooli son  */
	for (cpt = 0; cpt < wav->subTaille2/2; cpt+=2) 
	{
		i += 1.0/44100.0;
		/* On met la même valeur pour les 2 enceintes */
		/* Essayez d'en mettre un à 0 et de laisser l'autre 
		 * Vous aurez un bruit que sur une seule enceinte  */
		data[cpt] = sin(2*M_PI/44100*cpt*frequence(-33.0+i)) * 16384;
		data[cpt+1] = sin(2*M_PI/44100*cpt*frequence(3.0-i)) * 16384;
	}
	
}
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 23:22:39

Ton programme me fait vachement délirer, je viens de voir qu'on pouvait faire de la random music (il suffit de jouer avec rand()!)
  • Partager sur Facebook
  • Partager sur Twitter
28 novembre 2009 à 23:27:35

Oui après tu peux faire ce que tu veux ^^
Si tu veux pousser un peu plus il faut que tu regardes du côté des séries de Fourier ;)
«Tout signal peut être décomposée en une série (finie ou infinie) de courbes sinusoïdales»
  • Partager sur Facebook
  • Partager sur Twitter
29 novembre 2009 à 5:53:44

Citation : Pouet_forever

dites moi si mon code compile sous VS parce qu'apparemment ya des trucs qu'il aime pas >_<



Il aime pas le C99, c'est tout :p
Il ne faut donc pas inclure ton header si on compile avec VS:
#ifdef _MSC_VER
#include <windows.h>
#define _USE_MATH_DEFINES /* Pour avoir M_PI, qui n'est pas standard d'après les commentaires du math.h de Microsoft */
typedef INT16 int16_t;
typedef INT32 int32_t;
#else
#include <stdint.h>
#endif


Cela est à placer avant d'inclure math.h évidement.
Il n'aime pas non plus le
union inverse inv = { .n = 0 };

dans convertirEndian(), c'est une extension de GCC, donc pas standard. J'ai remplacé par ça:
union inverse inv = {0};


Et là ça compile et ça s'exécute comme il faut :)
  • Partager sur Facebook
  • Partager sur Twitter
29 novembre 2009 à 11:15:03

Citation : [ZBM]

c'est une extension de GCC, donc pas standard.


Hé ben en fait si c'est standard :)
J'avais chopé ça dans la norme ^^
(j'ai la flème de lire je mets juste l'exemple :-° )

Citation : Norme C - §6.7.8-38

EXAMPLE 13 Any member of a union can be initialized:
union { /* ... */ } u = { .any_member = 42 };


Et puis bon pour l'initialiser à 0 ya pas besoin de faire ça :p C'est juste que dans mes tests je mettais une autre valeur c'est pour ça ^^

Et non apparemment M_PI n'est pas standart c'est pour ça que je l'ai défini dans mon code. J'ai juste fait un copier / coller ^^
J'édite mon code :)
Et tu peux me dire si maintenant le code compile sans problème sous VS (ouf ? :D )
  • Partager sur Facebook
  • Partager sur Twitter
29 novembre 2009 à 16:33:41

T'as laissé le #include <stdint.h> à la ligne 12, sinon ça compile (il reste les deux warnings dans creerNote(), mais c'est pas propre à VS).
  • Partager sur Facebook
  • Partager sur Twitter
29 novembre 2009 à 16:42:54

C'est édité ^^
Sinon pour les warning c'est quoi ? :euh:
Un problème de cast ? :euh:
Pourtant ma fonction retourne un int16_t :x
  • Partager sur Facebook
  • Partager sur Twitter
29 novembre 2009 à 17:04:58

convertirEndian() prend un int16_t, mais sin() retourne un double ;)
  • Partager sur Facebook
  • Partager sur Twitter
16 décembre 2009 à 6:00:36

Bonjour,
Pour les valeurs codées sur 16 bits tu indiques que la valeur peut aller de -32268 à 32267. Ne serait-ce pas plutôt de -32767 à +32767 ?
  • Partager sur Facebook
  • Partager sur Twitter