Les concepts de base de la Web Audio API

Cet article explique une partie de la théorie sur laquelle s'appuient les fonctionnalités de la Web Audio API. Il ne fera pas de vous un ingénieur du son, mais vous donnera les bases nécessaires pour comprendre pourquoi la Web Audio API fonctionne de cette manière, et vous permettre de mieux l'utiliser.

Graphes audio

La Web Audio API implique d'effectuer le traitement du son dans un contexte audio; elle a été conçue sur le principe de routage modulaire. Les opérations basiques sont effectuées dans noeuds audio, qui sont liés entre eux pour former un graphe de routage audio. Un seul contexte peut supporter plusieurs sources — avec différentes configurations de canaux. Cette architecture modulaire assure la flexibilité nécessaire pour créer des fonctions audio complexes avec des effets dynamiques.

Les noeuds audio sont liés au niveau de leur entrée et leur sortie, formant une chaîne qui commence avec une ou plusieurs sources, traverse un ou plusieurs noeuds, et se termine avec une sortie spécifique (bien qu'il ne soit pas nécessaire de spécifier une sortie si, par exemple, vous souhaitez seulement visualiser des données audio). Un scénario simple, représentatif de la Web Audio API, pourrait ressembler à ceci :

  1. Création d'un contexte audio
  2. Dans ce contexte, création des sources — telles que <audio>, oscillateur, flux
  3. Création des noeuds d'effets, tels que réverb, filtres biquad, balance, compresseur
  4. Choix final de la sortie audio, par exemple les enceintes du système
  5. Connection des sources aux effets, et des effets à la sortie.

Diagramme simple composé de trois rectangles intitulés Sources, Effets et Sortie, reliés par des flèches, de gauche à droite, qui indiquent le sens du flux d'informations audio.

Chaque entrée ou sortie est composée de plusieurs canaux, chacun correspondant à une configuration audio spécifique. Tout type de canal discret est supporté, y compris mono, stereo, quad, 5.1, etc.

Diagramme qui montre comment les AudioNodes sont reliés par leurs entrées et sorties, et la configuration des canaux à l'intérieur de ces entrées/sorties.

Les sources audio peuvent être de provenance variée :

  • générées directement en JavaScript avec un noeud audio (tel qu'un oscillateur)
  • créées à partir de données PCM brutes (le contexte audio a des méthodes pour décoder les formats audio supportés)
  • fournies par une balise HTML media (telle que <video> ou <audio>)
  • récupérées directement avec WebRTC MediaStream (une webcam ou un microphone)

Données audio: ce qu'on trouve dans un échantillon

Lors du traitement d'un signal audio, l'échantillonage désigne la conversion d'un signal continu en signal discret; formulé autrement, une onde de son continue, comme un groupe qui joue en live, est convertie en une séquence d'échantillons (un signal temporel discret) qui permet à l'ordinateur de traiter le son en blocs distincts.

On peut trouver davantage de détails sur la page Wikipédia Echantillonage (signal).

Mémoire tampon : trames, échantillons et canaux

Un AudioBuffer prend comme paramètres un nombre de canaux (1 pour mono, 2 pour stéréo, etc), une longueur, qui correspond au nombre de trames d'échantillon dans la mémoire tampon, et un taux d'échantillonage, qui indique le nombre de trames d'échantillons lues par seconde.

Un échantillon est une valeur float32 unique, qui correspond à la valeur du flux audio à un point précis dans le temps, sur un canal spécifique (gauche ou droit dans le cas de la stéréo). Une trame, ou trame d'échantillon est l'ensemble de toutes les valeurs pour tous les canaux (deux pour la stéréo, six pour le 5.1, etc.) à un point précis dans le temps.

Le taux d'échantillonage est le nombre d'échantillons (ou de trames, puisque tous les échantillons d'une trame jouent en même temps) qui sont joués en une seconde, exprimés en Hz. Plus le taux d'échantillonage est élevé, meilleure est la qualité du son.

Prenons deux AudioBuffer, l'un en mono et l'autre en stéréo, chacun d'une durée de 1 seconde et d'une fréquence de 44100Hz:

  • le mono aura 44100 échantillons, et 44100 trames. Sa propriété length vaudra 44100.
  • le stéréo aura 88200 échantillons, et 44100 trames. Sa propriété length vaudra aussi 44100, puisqu'elle correspond au nombre de trames.

Le diagramme montre une succession de tames dans un buffer audio. Comme le buffer est composé de deux canaux (stéréo), chaque trame contient deux échantillons.

Lorsqu'un noeud de mémoire tampon est lu, on entend d'abord la trame la trame la plus à gauche, puis celle qui la suit à droite, etc. Dans le cas de la stéréo, on entend les deux canaux en même temps. Les trames d'échantillon sont très utiles, car elles représentent le temps indépendamment du nombre de canaux.

Note : Pour obtenir le temps en secondes à partir du nombre de trames, diviser le nombre de trames par le taux d'échantillonage. Pour obtenir le nombre de trames à partir du nombre d'échantillons, diviser le nombre d'échantillons par le nombre de canaux.

Voici quelques exemples simples:

js
var contexte = new AudioContext();
var memoireTampon = contexte.createBuffer(2, 22050, 44100);

Note : 44,100 Hz (que l'on peut aussi écrire 44.1 kHz) est un taux d'échantillonage couramment utilisé. Pourquoi 44.1kHz ?

D'abord, parce ce que le champ auditif qui peut être perçu par des oreilles humaines se situe à peu près entre 20 Hz et 20,000 Hz, et que selon le théorème d'échantillonage de Nyquist–Shannon la fréquence d'échantillonage doit être supérieure à deux fois la fréquence maximum que l'on souhaite reproduire; le taux d'échantillonage doit donc être supérieur à 40 kHz.

De plus, le signal doit être traité par un filtre passe-bas avant d'être échantilloné, afin d'éviter le phénomène d'aliasing, et, si en théorie un filtre passe-bas idéal devrait être capable de laisser passer les fréquences inférieures à 20 kHz (sans les atténuer) et de couper parfaitement les fréquences supérieures à 20 kHz, en pratique une bande de transition dans laquelle les fréquences sont partiellement atténuées est nécessaire. Plus la bande de transition est large, plus il est facile et économique de faire un filtre anti-aliasing. Le taux d'échantillonage 44.1 kHz laisse une bande de transition de 2.05 kHz.

Ce code génère une mémoire tampon stéréo (deux canaux) qui, lorsqu'elle est lue dans un AudioContext à 44100Hz (configuration répandue, la plupart des cartes sons tournant à cette fréquence), dure 0.5 secondes: 22050 trames / 44100Hz = 0.5 secondes.

js
var contexte = new AudioContext();
var memoireTampon = context.createBuffer(1, 22050, 22050);

Ce code génère une mémoire tampon mono (un seul canal) qui, lorsqu'elle est lue dans un AudioContext à 44100Hzz, est automatiquement *rééchantillonnée* à 44100Hz (et par conséquent produit 44100 trames), et dure 1.0 seconde: 44100 frames / 44100Hz = 1 seconde.

Note : Le rééchantillonnage audio est très similaire à la redimension d'une image : imaginons que vous ayiez une image de 16 x 16, mais que vous vouliez remplir une surface de 32x32: vous la redimensionnez (rééchantillonnez). Le résultat est de qualité inférieure (il peut être flou ou crénelé, en fonction de l'algorithme de redimensionnement), mais cela fonctionne, et l'image redimensionnée prend moins de place que l'originale. C'est la même chose pour le rééchantillonnage audio — vous gagnez de la place, mais en pratique il sera difficle de reproduire correctement des contenus de haute fréquence (c'est-à-dire des sons aigus).

Mémoire tampon linéaire ou entrelacée

La Web Audio API utilise un format de mémoire tampon linéaire : les canaux gauche et droite sont stockés de la façon suivante :

LLLLLLLLLLLLLLLLRRRRRRRRRRRRRRRR (pour un buffer de 16 trames)

C'est assez courant dans le traitement audio, car cela permet de traiter facilement chaque canal de façon indépendante.

L'alternative est d'utiliser un format entrelacé:

LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLR (pour un buffer de 16 trames)

Ce format est communément utilisé pour stocker et lire du son avec très peu de traitement, comme par exemple pour un flux de MP3 décodé.

La Web Audio API expose *uniquement* des buffer linéaires, car elle est faite pour le traitement du son. Elle fonctionne en linéaire, mais convertit les données au format entrelacé au moment de les envoyer à la carte son pour qu'elles soient jouées. A l'inverse, lorsqu'un MP3 est décodé, le format d'origine entrelacé est converti en linéaire pour le traitement.

Canaux audio

Une mémoire tampon audio peut contenir différents nombres de canaux, depuis les configurations simple mono (un seul canal) ou stéréo (canal gauche et canal droit) en allant jusquà des configurations plus complexe comme le quad ou le 5.1, pour lesquels chaque canal contient plusieurs échantillons de sons, ce qui permet une expérience sonore plus riche. Les canaux sont généralement représentés par les abbréviations standard détaillées dans le tableau ci-après :

Mono 0: M: mono
Stereo 0: L: gauche
1: R: droit
Quad 0: L: gauche
1: R: droit
2: SL: surround gauche
3: SR: surround droit
5.1 0: L: gauche
1: R: droit
2: C: centre
3: LFE: subwoofer
4: SL: surround gauche
5: SR: surround droit

Conversion ascendante et descendante

Lorsque le nombre de canaux n'est pas le même en entrée et en sortie, on effectue une conversion ascendante ou descendante selon les règles suivantes. Cela peut être plus ou moins controllé en assignant la valeur speakers ou discrete à la propriété AudioNode.channelInterpretation .

Interprétation Canaux d'entrée Canaux de sortie Règles de conversion
speakers 1 (Mono) 2 (Stéréo) Conversion ascendante de mono vers stéréo.
Le canal d'entrée M est utilisé pour les deux canaux de sortie (L et R).
output.L = input.M
output.R = input.M
1 (Mono) 4 (Quad) Conversion ascendante de mono vers quad.
Le canal d'entrée M est utilisé pour les canaux de sortie autres que surround (L et R). Les canaux de sortie surround (SL et SR) sont silencieux.
output.L = input.M
output.R = input.M
output.SL = 0
output.SR = 0
1 (Mono) 6 (5.1) Conversion ascendante de mono vers 5.1.
Le canal d'entrée M est utilisé pour le canal de sortie central (C). Tous les autres canaux (L, R, LFE, SL, et SR) sont silencieux.
output.L = 0
output.R = 0

output.C = input.M
output.LFE = 0
output.SL = 0
output.SR = 0
2 (Stéréo) 1 (Mono) Conversion descendante de stéréo vers mono.
Les deux canaux d'entrée (L et R) sont combinées pour produire l'unique canal de sortie (M).
output.M = 0.5 * (input.L + input.R)
2 (Stéréo) 4 (Quad) Conversion ascendante de stéréo vers quad.
Les canaux d'entrée L et R input sont utilisés pour leurs équivalents respectifs non-surround en sortie (L et R). Les canaux de sortie surround (SL et SR) sont silencieux.
output.L = input.L
output.R = input.R
output.SL = 0
output.SR = 0
2 (Stéréo) 6 (5.1) Conversion ascendante de stéréo vers 5.1.
Les canaux d'entrée L et R sont utilisés pour leurs équivalents respectifs non-surround en sortie (L et R). Les canaux de sortie surround (SL et SR), ainsi que le canal central (C) et le canal subwoofer (LFE) restent silencieux.
output.L = input.L
output.R = input.R
output.C = 0
output.LFE = 0
output.SL = 0
output.SR = 0
4 (Quad) 1 (Mono) Conversion descendante de quad vers mono.
Les quatre canaux de sortie (L, R, SL, et SR) sont combinés pour produire l'unique canal de sortie (M).
output.M = 0.25 * (input.L + input.R + input.SL + input.SR)
4 (Quad) 2 (Stéréo) Conversion descendante de quad vers stéréo.
Les deux canaux d'entrée à gauche (L and SL) sont combinés pour produire l'unique canal de sortie à gauche (L). De la même façon, les deux canaux d'entrée à droite (R et SR) sont combinés pour produire l'unique canal de sortie à droite (R).
output.L = 0.5 * (input.L + input.SL)
output.R = 0.5 * (input.R + input.SR)
4 (Quad) 6 (5.1) Conversion ascendante de quad vers 5.1.
Les canaux d'entrée L, R, SL, et SR sont utilisés pour leur canaux de sortie équivalents respectifs (L and R). Le canal central (C) et le canal subwoofer (LFE) restent silencieux.
output.L = input.L
output.R = input.R
output.C = 0
output.LFE = 0
output.SL = input.SL
output.SR = input.SR
6 (5.1) 1 (Mono) Conversion descendante de 5.1 vers mono.
Les canaux de gauche (L et SL), de droite (R et SR) et central sont tous mixés ensemble. Les canaux surround sont légèrement atténués et la puissance des canaux latéraux est compensée pour la faire compter comme un seul canal en la multipliant par √2/2. Le canal subwoofer (LFE) est perdu.
output.M = 0.7071 * (input.L + input.R) + input.C + 0.5 * (input.SL + input.SR)
6 (5.1) 2 (Stéréo) Conversion descendante de 5.1 vers stéréo.
Le canal central (C) est additionné avec chacun des canaux latéraux (SL et SR) puis combiné avec chacun des canaux latéraux (L et R). Comme il est converti en deux canaux, il est mixé à une puissance inférieure : multiplié par √2/2. Le canal subwoofer (LFE) est perdu.
output.L = input.L + 0.7071 * (input.C + input.SL)
output.R = input.R
+ 0.7071 * (input.C + input.SR)
6 (5.1) 4 (Quad) Conversion descendante de 5.1 vers quad.
Le canal central (C) est combiné avec les canaux latéraux non-surround (L et R). Comme il est converti en deux canaux, il est mixé à une puissance inférieure : multiplié par √2/2. Les canaux surround restent inchangés. Le canal subwoofer (LFE) est perdu.
output.L = input.L + 0.7071 * input.C
output.R = input.R + 0.7071 * input.C
output.SL = input.SL
output.SR = input.SR
Autres configurations non-standard Les configurations non-standard sont traitées comme si la propriété channelInterpretation avait la valeur discrete.
La spécification autorise explicitement la définition à venir de nouvelles configurations de sortie pour les enceintes. Ce cas de figure n'est par conséquent pas garanti dans le futur, car le comportement des navigateurs pour un nombre spécifique de canaux pourrait être amené à changer.
discrete tout (x) tout (y) pour lequel x<y Conversion ascendante de canaux discrets.
Remplit chaque canal de sortie avec son équivalent en entrée, c'est-à-dire le canal qui a le même index. Les canaux de sortie qui n'ont pas d'équivalent en entrée restent silencieux.
tout (x) tout (y) pour lequel x>y Conversion descendante de canaux discrets.
Remplit chaque canal de sortie avec son équivalent en entrée, c'est-à-dire le canal qui a le même index. Les canaux d'entrée qui n'ont pas d'équivalent en sortie sont perdus.

Visualisations

Une visualisation audio consiste en général à utiliser un flux de données audio dans le temps (souvent des informations de gain ou de fréquence) pour générer un affichage graphique (comme un graphe). La Web Audio API possède un AnalyserNode qui n'altère pas le signal audio qui le traverse, permettant de générer des données qui peuvent être utilisées par une technologie de visualisation telle que <canvas>.

Le noeud permet de récupérer la fréquence et le domaine temporel en utilisant FFT, et ce sans modifier le flux audio

On peut accéder aux données en utilisant les méthodes suivantes:

AnalyserNode.getFloatFrequencyData()

Copie les données de fréquence dans le tableau Float32Array passé en argument.

AnalyserNode.getByteFrequencyData()

Copies les données de fréquence dans le tableau d'octets non signés Uint8Array passé en argument.

AnalyserNode.getFloatTimeDomainData()

Copie les données de l'onde de forme, ou domaine temporel, dans le Float32Array passé en argument.

AnalyserNode.getByteTimeDomainData()

Copie les données de l'onde de forme, ou domaine temporel, dans le tableau d'octets non signés Uint8Array passé en argument.

Note : Pour plus d'informations, voir notre article Visualizations with Web Audio API.

Spatialisations

Une spatialisation audio (gérée par les noeuds PannerNode et AudioListener dans la Web Audio API) permet de modéliser la position et le comportement d'un signal audio situé dans l'espace, ainsi que l'auditeur qui perçoit ce signal.

La position du panoramique est décrite avec des coodonnées cartésiennes selon la règle de la main droite, son mouvement à l'aide d'un vecteur de vélocité (nécessaire pour la création d'effets Doppler) et sa direction avec un cone de direction. Le cone peut être très large, par exemple dans le cas de sources omnidirectionnelles.

Le PannerNode donne la position dans l'espace, la vélocité et la direction d'un signal donné

La position de l'auditeur est décrite avec des coodonnées cartésiennes selon la règle de la main droite, son mouvement à l'aide d'un vecteur de vélocité et la direction vers laquelle elle pointe en utilisant deux vecteurs de direction : haut et face. Ceux-ci définissent respectivement la direction vers laquelle pointent le haut de la tête et le bout du nez de l'auditeur, et forment un angle droit entre eux.

On voit la position d'un auditeur, ainsi que les vecteurs de direction haut et de face qui forment un angle de 90°

Note : For more information, see our Web audio spatialization basics article.

Fan-in et Fan-out

En audio, fan-in désigne le processus par lequel un ChannelMergerNode prend une série d'entrées mono entrée et restitue un seul signal multi-canaux :

Fan-out désigne le processus opposé, par lequel un ChannelSplitterNode prend une source multi-canaux en entrée et restitue plusieurs signaux mono en sortie: