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 :
- Création d'un contexte audio
- Dans ce contexte, création des sources — telles que
<audio>
, oscillateur, flux - Création des noeuds d'effets, tels que réverb, filtres biquad, balance, compresseur
- Choix final de la sortie audio, par exemple les enceintes du système
- Connection des sources aux effets, et des effets à la sortie.
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.
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.
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:
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.
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 |
Quad | 0: L: gauche |
5.1 | 0: L: gauche |
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
|
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
|
|
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.C = input.M
|
|
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
|
|
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
|
|
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
|
|
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) + 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
|
|
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>
.
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.
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.
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: