Un son monophonique est un signal `u`, c'est à dire une fonction du temps `t`, que l'on déclare par le neurone `u"←"(:t:)`. Dans la pratique, on ne connait pas la définition de cette fonction, mais seulement ses valeurs à des instants réguliers, `u(:0:)`, `u(:dt:)`, `u(:2dt:)`, `u(:3dt:)` .... On dit que le signal est échantillonné à la fréquence `1"/"dt`, ou plus simplement qu'il est échantillonné par intervalle de temps `dt`. Et c'est ainsi qu'il est mémorisé.
Le signal est un concept qui justifie pleinement la définition d'une norme, d'une codification, pour pouvoir mémoriser un signal, de façons la plus interopérable possible. Comment définir une norme de fichier pour mémoriser un signal ? Pour répondre à cette question, il faut d'abord répondre à la question suivante : Comment définir une norme pour mémoriser des mesures physiques que constitue des nombres avec une précision commune et une unité commune ou plutôt une échelle commune, car on ne se préoccupe pas de la nature de l'unité juste de sa grandeur comme une puissance de `10`.
Le théorème de l'échantillonnage de C. E. Shannon dit : « Un signal qui ne contient pas de composante de fréquence supérieure ou égale à une valeur `nu`, est entièrement déterminé par la suite de ses valeurs à des instants régulièrement espacés par la demie période de la fréquence `nu`. C'est à dire qu'il peut être échantillonné par intervalle de temps `1"/"(2nu)`, ou dit autrement, qu'il peut être échantillonné à la fréquence `2nu`, sans qu'il n'y est aucune perte d'information. »
Si nous nous intéressons à la musique, l'oreille ne perçevant pas les fréquences supérieures à `20 000 "Hz"`, il convient d'appliquer un filtre qui coupe toutes les fréquences supérieure à 40 000 Hertz, et il est alors inutile d'échantillonner les sons à des fréquences plus grande que `40 000 "Hz"`. Un standart courant dans le monde de la musique est l'échantillonnage à `44 100 "Hz"`.
Le signal est une valeur évoluant avec le temps. Mais on a pas précisé ce que l'on entendait par valeur. On peut utiliser des valeurs beaucoup plus générale que de simples réels. L'ensemble des valeurs munis de l'addition et de la multiplication a pour seule contrainte de former un corps dans lequel il existe une métrique permettant de définir l'exponentielle :
`e^x = sum_(n in NN) x^n/(n!) = 1+x+x^2/2+x^3/(3!) + x^4/(4!) + ... `
avec comme convention que `0^0"="0!"="1`. Et on citera des exemples de tel corps : Les réels `RR`, les complexes `CC`, les quaternions `bbbH`. La valeur comprend alors une ou deux ou quatre composantes réels.
Puis on considère une approximation sur chacune de ces composantes, un nombre de chiffres significatifs dans leur développement décimal, ou plus précisement, un nombre de chiffres binaires significatifs dans leur développement binaire, les chiffres binaires suivants étant abandonnées.
Le signal est donc quantifié. Et il existe une perte d'information. La valeur du signal est mémorisée dans un nombre de bits données, à une précision près, correspondant au nombre de bits en question.
Il parait logique qu'une valeur du signal, qui constitue une mesure, doit tenir en mémoire sur un nombre de bits correspondant à sa précision commune. Le résultat de cela est un signal relatif à une origine et à une échelle. Il faut dont le compléter de ces deux constantes que sont l'origine et l'échelle pour le définir dans l'absolu.
On utilise la représentation classique des entiers relatifs en binaires. La valeur entière mémorisée dans `n` bit est comprise dans l'intervalle suivant :
`[-2^(n-1) , 2^(n-1)-1]`
`[-128 , 127]` // exemple pour `n"="8`
On maxi-norme cette valeur en la divisant par `2^(n-1)`. Cela produit un nombre compris dans l'intervalle suivant :
`[-1, 1-2^-(n-1))]`
`[-1, 1-1/128]` // exemple pour `n"="8`
On constate une petite disymétrie. On rend l'implémentation symétrique en interdisant la valeur -1 qui correspond à la copnfiguration binaire `1000...0`. Cette valeur sera réservée pour un autre usage. Délors les valeurs représentées sont comprise dans l'intervalle suivant :
`[-(1/2-2^-(n-1)) , 1/2-2^-(n-1)]`
`[-(1/2-1/128), 1/2-1/128]` // exemple pour `n"="8`
Et donc à ce stade, notre signal utilise des quantas de valeurs égale à `2^(-(n-1))`, c'est à dire pour `n "=" 8`, à des quantas de `1"/"128`. C'est la plus petite grandeur mémorisable sur un bloc de `n` bits représentant un entier relatif.
Le signal est parfois défini à une constante près, sauf si on considère que cette constante fait partie du signal comme étant sa composante continue, ou dit autrement, sa composante de fréquence `oo`. Il en est de même pour l'échelle. Ainsi, le signal se scinde en deux parties ; une entête contenant ces deux constantes, l'origine et l'échelle, et un corps contenant le signal relatif.
La loi de Weber-Fechner affirme que la sensation `S` varie proportionnellement au logarithme de l'excitation `x`. Mais la base du logarithme qui correspond au facteur `k` de proportionnalité reste une inconnue.
`S = k ln(x)`
Tout d'abord rappelons les règles de calcul sur les logarithmes. Considérons le logaritme en base `P` noté `log_P(".")` ainsi que le logarithme en base `e` qui est appelé le logarithme népérien noté `ln(".")` :
`ln(1) = 0`
`ln(e) = 1`
`e^(ln(x)) = x`
`ln(xy) = ln(x)+ln(y)`
`ln(ex) = ln(x)+1`
`ln(x^y) = y ln(x)`
`ln(e^x) = x`
`ln(x) = (log_P(x))/(log_P(e))`
`log_P(1) = 0`
`log_P(P) = 1`
`P^(log_P(x)) = x`
`log_P(xy) = log_P(x)+log_P(y)`
`log_P(Px) = log_P(x)+1`
`log_P(x^y) = y log_P(x)`
`log_P(P^x) = x`
`log_P(x) = (ln(x))/(ln(P))`
Une variation de `+1` du logarithme en base `P` correspond à une multiplication de son argument par `P`.
Une variation de `+1` du logarithme néperien correspond à une multiplication de son argument par `e≃2.7`.
Les propriétés mathématiques de l'exponentielle, du logarithme et de l'opérateur de dérivée, mettent en avant une base canonique qu'est la base néperienne `e` avec laquelle nous avons les propriétés remarquables suivantes :
`(d(e^x))/dx = e^x`
`(d(ln(x)))/dx = 1/x`
Ce constat théorique à donné lieu à la définition du néper, de symbole `"Np"`, comme étant le logarithme néperien du rapport des signaux. Une variation de `+1` néper désigne une multiplication du signal par `e≃2.7`.
Pour chaque base `P` du logarithme, correspond un facteur `k=1"/"ln(P)` de proportonnalité logarithmique. Et réciproquement pour chaque facteur `k` de proportonnalité logarithmique, correspond une base `P = e^(1/k)` du logarithme :
`log_P(x) = 1/ln(P) ln(x)`
`log_(e^(1/k))(x) = k ln(x)`
L'expression de la sensation `S` en fonction de l'excitation `x` est d'après Weber-Fechner :
`S = k ln(x) = log_(e^((1/k))) (x)`
où le facteur de proportionnalité `k` est inconnue. L'expérience nous montre que la sensation physiologique du volume sonore double lorsque la puissance (la quantité d'énergie transportée par seconde) du signal sonore est multipliée par `≃10`. Le chiffre est évidement trés approximatif et le fait que ce chiffre soit égale à `10` n'est qu'une pure coïncidence, fruit de l'expérimentation.
Ce constat expérimental a donné lieu à la définition du Bel, de symbole `"B"`, comme étant le logarithme en base `10` du rapport des énergies. Ainsi l'augmentation d'un Bel, qui correspond à une sensation de doublement du volume sonore, correspond à une multiplication par `10` de l'énergie. Mais comme l'énergie correspond au carré de la valeur du signal, nous en déduisons que l'augmentation d'un Bel correspond à une multiplication par `sqrt(10) ≃ 3.16` du signal. Ainsi le nombre de Bel d'un signal `S` relativement à un signal `S_0` est donnée par la formule suivante :
`log_sqrt(10)(S/S_0) = 1/ln(sqrt(10)) ln(S/S_0)`
On en conclut que `1` néper est égale à `1"/"ln(sqrt(10))` Bel.
`1 "Np" ≃ 0.87 "B"`
`1 "Np" ≃ 8.7 "dB"``1 "B" ≃ 1.15 "Np"`
`1 "dB" ≃ 0.115 "Np"`
La perte d'information due à la quantification fait disparaître les signaux de faible intensité. Une valeur mémorisée sur `n "bit"`, est un nombre entier relatif de quantas. Chaque quanta ayant la valeur `2^("-"(n"-"1))`. Un tel quanta représente la valeur la plus faible en intensité qui peut être mémorisée.
Si on pose la valeur maximal qui sert de borne exclusive et qui est la valeur `1`, comme désignant le niveau sonore zéro. Le niveau sonore du quanta sera de `"-"(n"-"1)ln(2) "Np"`, c'est à dire approximativement `"-"0.7(n"-"1) "Np"`, ou encore `"-"0.6(n"-"1) "B"`, ou encore `"-"6(n"-"1) "dB"`.
Un standart courant dans le monde de la musique est la quantification sur `16 "bit"`, ce qui permet d'écouter des écarts d'intensité de son de 90 décibels ou de 9 Bels ou de 10 népers, `90 "dB" = 9 "B" ≃ 10 "Np"`.
On se propose de regrouper dans un fichier texte toutes les données écrites en claire, c'est à dire lisible par un humain, nécessaires et suffisantes pour définir un signal.
Nous pourrions utiliser le format XML, mais nous le trouvons trop verbeux. Quelques conventions intelligentes sur le format de fichier texte suffisent. En cela, nous créons un format de fichier texte comparable au format XML mais beaucoup moins verbeux.
Les paramètres sont disposés ligne par ligne sous la forme Nom Valeur(s) où le caractère blanc joue le rôle de séparateur, et on commence comme pour le XML par :
Version 1.0
Codage UTF-8
Si ces paramètres ne sont pas mentionnés, leur valeur par défaut seront 1.0 et UTF-8.
Puis on crée le paramètre Format pour indiquer le type de fichier texte. Nous pourrions intégrer les paramètres de version et de codage dans le paramètre Format. La valeur par défaut est le mot Texte, et désigne un texte quelconque. Mais pour ce qui nous intéresse, la valeur sera PCM :
Format PCM
Le terme PCM (en anglais Pulse-Code Modulation) désigne la représentation numérique d'un signal échantillonné. Vient alors les 2 premiers paramètres que sont la fréquence d'échantillonnage et le nombre de bits pour mémoriser une valeur du signal et qui correspond à sa précision commune.
Fréquence 44100
Bits 16
Puis vient les 2 paramètres que sont l'origine et l'échelle, ici par défaut :
Échelle 1
Origine 0
Une valeur se présente par un nombre décimal signé avec ou sans point appelé mantisse, le point n'étant ni au début ni à la fin de la séquence de décimal, suivi éventuellement du symbole E et d'un nombre décimal signé sans point :
Échelle 1.5
Origine -12.4021E-3
Puis vient la succession des valeurs des mots de 16 bits mis sous forme d'entier relatif en notation décimale, compris entre `-(2^15-1)` et `2^15"-"1`, un entier par ligne. Cette partie est annoncé par le mot clef Liste en première ligne.
Liste
25
123
-45
-142
...
On a beau vouloir faire quelque chose de lisible et non verbeux, cela reste encore verbeux et transporte inévitbalement un peu les lourdeurs du format XML.
Une version plus courte serait :
Liste
44100
16
1.5
-12.4021E-3
25
123
-45
-142
...
où la signification des éléments de la liste serait contenus ailleurs, ce qui entraine une perte d'information haute sur la nature du fichier. On peut y remédier facilement en rétablissant la référence par un mot clef tel que SignalPCM. Le fichier texte devient alors :
SignalPCM
44100
16
1.5
-12.4021E-3
25
123
-45
-142
...
Les noms de paramètres oumots clefs sont alors répertoriés dans un versionning d'une norme des fichiers textes.
L'information brute doit pouvoir être réduite en taille de façon canonique, c'est à dire de façon optimale dans les cas qui sont les pires. L'essentielle de l'information tient dans les échantillons et sont déjà réduit canoniquement en un nombre de bits fixe. Reste le label du format et les 4 paramètres qui occupent une taille négligeable.
Une chaine de taille variable est définie par son caractère de fin de chaine. Pour les nombres, il n'y a pas de norme aussi convenue, et s'il ne parait pas utile d'en frabriquer une pour notre cas, alors on choisit d'écrire le nombre en claire comme une chaine de caractère sous le format scientifique telque par exemple -123.4567E-562
Si notre fichier doit être transmis, une transmission qui peut être altérée et que le destinataire doit commencer à traiter la partie qu'il a commencé à télécharger avant même d'avoir tout téléchargé, alors d'autres logiques se mettent en place. Le fichier devient un message. L'information transmise, si elle est indispensable, doit être répétée. Chaque groupes de valeurs doit être marquée par un début, pour permettre en cas de perte d'une partie du message, de pouvoir se recaler sur les valeurs suivantes du signal, le nombre de bits par valeurs est alors déductible en analysant le fichier. De même il doit y avoir des marqueurs de temps à chaque intervalle de temps, faisant que la fréquence d'échantillonage est déductible en analysant le fichier. Et s'il faut transmettre des valeurs absolues, une composante constante et un étalon, on n'échappera pas à la nécessité de les transmettre plusieurs fois et de façon régulière tout au long du message.
---- 24 décembre 2020 ----
Le son mono et PCM est mémorisé dans un fichier wav selon le format suivant :
N° octet |
Nombre d'octets |
Libéllé |
Variable |
Description |
Exemple |
0 |
4 |
ChunkID |
= "RIFF" | "RIFF" |
|
4 |
4 |
ChunkSize |
= 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size) |
882036 |
|
8 |
4 |
Format |
= "WAVE" | "WAVE" |
|
12 |
4 |
Subchunk1ID |
= "fmt " | "fmt " |
|
16 |
4 |
Subchunk1Size |
= 16 | 16 |
|
20 |
2 |
AudioFormat |
= 1 | 1 |
|
22 |
2 |
NumChannels |
M |
Nombres de voix (1 = mono) | 1 |
24 |
4 |
SampleRate |
f |
Fréquence d'échantillonnage en hertz | 44100 |
28 |
4 |
ByteRate |
f*M*b/8 |
= SampleRate * NumChannels * BitsPerSample/8 | 88200 |
32 |
2 |
BlockAlign |
M*b/8 |
= NumChannels * BitsPerSample/8 | 2 |
34 |
2 |
BitsPerSample |
b |
Nombre de bits quantifiant la valeur du signal (est égale à 8,16, 24 ou 32) |
16 |
36 |
4 |
Subchunk2ID |
= "data" | "data" |
|
40 |
4 |
Subchunk2Size |
n*M*b/8 |
Taille des données qui suivent = NumSamples * NumChannels * BitsPerSample/8 |
882000 |
44 |
2 |
sample0 |
x[0] |
Valeur du signal à l'instant 0*dt | 5652 |
46 |
2 |
sample1 |
x[1] |
Valeur du signal à l'instant 1*dt | 31052 |
48 |
2 |
sample2 |
x[2] |
Valeur du signal à l'instant 2*dt | -22041 |
... |
... |
... |
... |
... | ... |
C'est un fichier binaire et nous devons donc préciser son endianess :
Les nombres entiers sont représentées sur plusieurs octets. L'ordre dans lequel ces octets sont organisés en mémoire ou dans une communication, est appelé l'endianness (traduction boutisme, déterminant par quel bout on commence). L'endianness little-endian (traduction petit-boutiste, petit bout d'abord) stipule que les premiers octets transmis, ou mémorisés dans les premières adresses, sont ceux de poid faible. L'adresse des octects correspond à leur poid. Autrement dit l'octet d'adresse plus élevé est de poid plus élevé. C'est l'endianess classique.
Ainsi les 2 octets du BitsPerSample sont :
l'octet n°34 contient la valeur 16
l'octet n°35 contient la valeur 0
Ainsi les 4 octets du SampleRate sont : 44100 = 68 + 172*256
l'octet n°24 contient la valeur 68
l'octet n°25 contient la valeur 172
l'octet n°26 contient la valeur 0
l'octet n°27 contient la valeur 0
Ainsi les 4 octets du Subchunk2Size sont : 882000 = 80 + 117*256 + 13*256*256
l'octet n°36 contient la valeur 80
l'octet n°37 contient la valeur 117
l'octet n°38 contient la valeur 13
l'octet n°39 contient la valeur 0
Les chaines de caractères sont transmises dans l'ordre de lecture. Les premiers octets transmis, ou mémorisés dans les premières adresses, sont les premières lettres du message. Noter que l'ordre lexicographique donne un poid plus élevé aux premières lettres, c'est pourquoi dans certain ouvrage l'endianness classique pour les chaines de caractères est dite big-endian (traduction gros-boutiste, gros bout d'abord).
Ainsi les 4 octets du ChunkID sont :
l'octet n°0 contient la valeur 82 qui est le code ASCII de la lettre 'R'.
l'octet n°1 contient la valeur 73 qui est le code ASCII de la lettre 'I'.
l'octet n°2 contient la valeur 70 qui est le code ASCII de la lettre 'F'.
l'octet n°3 contient la valeur 70 qui est le code ASCII de la lettre 'F'.
On utilisera directement le fichier wav comme données d'entré ou de sortie d'un logiciel de calcul formel tel que Mupad ou d'un logiciel de script tel que Ruby.
---- 21 décembre 2020 ----