La Web Audio API offre un méchanisme à la fois simple et puissant pour implémenter et manipuler le contenu audio dans une application web. Elle permet de manipuler mixages audio, effets, balance, etc. Cet article donne les bases pour l'utiliser, à travers quelques exemples simples..
La Web Audio API ne vient pas remplacer l'élément <audio>, mais plutôt le compléter, de même que l'API Canvas 2D coexiste avec l'élément <video>. Lorsqu'on a juste besoin de contrôler la lecture d'un fichier audio, <audio> est probablement une meilleure solution, plus rapide. Si l'on veut procéder à un traitement audio plus complexe et à la lecture d'une source, la Web Audio API offre davantage de possibilités en termes de puissance et de contrôle.
L'une des choses qui rend la Web Audio API puissante est qu'elle n'a pas de limites strictes. Pas de plafond de 32 ou 64 appels en même temps, par exemple. En fonction de la puissance du processeur, il est possible de jouer un millier ou même davantage de sons en simultané sans saccades. C'est un vrai progrès, quand on sait qu'il y a quelques années les cartes son milieu à haut de gamme ne supportaient qu'une partie de cette charge.
Exemples
Afin d'expliquer l'utilisation de la Web Audio API, nous avons créé un certain nombre d'exemples qui seront étoffés au fur et à mesure. N'hésitez pas à en ajouter d'autres et à suggérer des améliorations !
Tout d'abord, nous avons créé le Voice-change-O-matic, une application web de déformation de la voix, qui permet de choisir différents effets et modes de visualisation. La qualité des effets pourrait sans aucun doute être améliorée, mais elle a le mérite de montrer l'utilisation de plusieurs fonctionnalités de la Web Audio API combinées ensemble (run the Voice-change-O-matic live).
Un autre exemple que nous avons créé dans notre quête pour comprendre la Web Audio API est le Violent Theremin, une application simple qui permet de changer le pitch et le volume d'un son en fonction du déplacement de la souris. Elle génère également des animations psychédéliques (see Violent Theremin source code).
Concepts de base
Note: la plupart des extraits de code dans cette section viennent de l'exemple Violent Theremin.
La Web Audio API impliqe de réaliser les opérations de traitement audio dans un contexte audio, et elle a été conçue pour permettre le routage modulaire. Les opérations de traitement de base sont réalisées par des noeuds audio, qui sont reliés entre eux pour former un graphe de routage audio. Plusieurs sources — avec différentes configuration de canaux — peuvent cohabiter dans un seul contexte. Ce design modulaire offre la flexibilité nécessaire pour créer des fonctions complexes avec des effets dynamiques.
Les noeuds audio sont reliés au niveau de leurs entrées et sorties, et forment une chaîne qui commence avec une ou plusieurs sources, traverse un ou plusieurs noeuds, et se termine en arrivant à sa destination (bien qu'il ne soit pas néessaire de préciser une destination si vous voulez simplement 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.
Création d'un contexte audio
Vous devez commencer par créer une instance de AudioContext
pour pouvoir créer un graphe audio. L'exemple le plus simple ressemblerait à ceci:
var contexteAudio = new AudioContext();
Note: On peut créer plusieurs contextes audio dans le même document, bien que ce soit probablement superflu dans la plupart des cas.
Il faut cependant rajouter une version préfixée pour les navigateurs Webkit/Blink browsers, tout en conservant la version non préfixée pour Firefox (desktop/mobile/OS). Ce qui donne :
var contexteAudio = new (window.AudioContext || window.webkitAudioContext)();
Note: Safari risque de planter si l'objet window
n'est pas explicitement mentionné lors de la création d'un contexte audio
Création d'une source audio
Maintenant que nous avons créé un contexte, nous pouvons utiliser les méthodes de ce contexte pour quasiment tout ce qui nous reste à faire. La première chose dont nous avons besoin est une source audio à manipuler. Les sources peuvent être de provenance diverse :The first thing we need is an audio source to play around with. Audio sources can come from a variety of places:
- générées en JavaScript par un noeud audio tel qu'un oscillateur. Pour créer un
OscillatorNode
on utilise la méthodeAudioContext.createOscillator
. - créées à partir de données PCM brutes: le contexte audio a des méthodes pour décoder lesformats supportés; voir
AudioContext.createBuffer()
,AudioContext.createBufferSource()
, etAudioContext.decodeAudioData()
. - récupérées dans des élements HTML tels que
<video>
ou<audio>
: voirAudioContext.createMediaElementSource()
. - prises dans un WebRTC
MediaStream
comme une webcam ou un microphone. VoirAudioContext.createMediaStreamSource()
.
Pour notre exemple nous nous contenterons de créer un oscillateur pour générer un son simple comme source, et un noeud de gain pour contrôler le volume:
var oscillateur = contexteAudio.createOscillator(); var noeudGain = contexteAudio.createGain();
Note: Pour jouer un fichier audio directement, il faut charger le fichier en XHR, le décoder en mémoire tampon, puis associer le tampon à une source. Voir l'exemple Voice-change-O-matic.
Note: Scott Michaud a écrit la librairie AudioSampleLoader, très pratique pour charger et décoder un ou plusieurs extraits audio. Elle peut aider à simplifier le processus de chargement XHR / mémoire tampon décrit dans la note précédente.
Lien entre la source et la destination audio
Pour faire sortir le son dans vos enceintes, il va falloir les relier. Pour cela on appelle la méthode connect
sur le noeud source, le noeud de destination étant passé en argument.
La sortie par défaut du matériel (en général les enceintes) est accessible via AudioContext.destination
. Pour connecter l'oscillateur, le noeud de gain et la destination, on écrirait les lignes suivantes:
oscillateur.connect(noeudGain); noeudGain.connect(contexteAudio.destination);
On peut connecter autant de noeuds qu'on le souhaite (cf. Voice-change-O-matic). Par exemple:
source = contexteAudio.createMediaStreamSource(stream); source.connect(analyser); analyser.connect(distortion); distortion.connect(biquadFilter); biquadFilter.connect(convolver); convolver.connect(noeudGain); noeudGain.connect(contexteAudio.destination);
Ce code créerait le graphe audio suivant :
Il est possible de connecter plusieurs noeuds à un seul noeud, par exemple pour mixer plusieurs sources ensemble, et les passer dans un seul noeud d'effet, tel qu'un noeud de gain.
Note: Depuis Firefox 32, les outils de développement intégrés incluent un éditeur audio, très utile pour débugger les graphes audio.
Lecture du son et paramétrage du pitch
Maintenant que le graphe audio est en place, nous pouvons modifier ses paramètres et appeler des méthodes sur les noeuds audio pour ajuster les effets qu'ils ont sur le son. Dans cet exemple nous spécifions un pitch en hertz pour notre oscillateur, lui assignons un type, et demandons au son de jouer tel quel :
oscillateur.type = 'sine'; // onde sinusoïdale — les autres valeurs possible sont : 'square', 'sawtooth', 'triangle' et 'custom' oscillateur.frequency.value = 2500; // valeur en hertz oscillateur.start();
Dans l'exemple Violent Theremin nous spécifions une valeur maximum pour le gain, et une valeur pour la fréquence:
var largeur = window.innerWidth; var hauteur = window.innerHeight; var frequenceMax = 6000; var volumeMax = 1; var frequenceInitiale = 3000; var volumeInitial = 0.5; // paramètres de l'oscillateur oscillateur.type = 'sine'; // onde sinusoïdale — les autres valeurs possible sont : 'square', 'sawtooth', 'triangle' et 'custom' oscillateur.frequency.value = frequenceInitiale; // valeur en hertz oscillateur.start(); noeudGain.gain.value = volumeInitial;
Puis nous réassignons les valeurs de fréquence et de pitch à chaque mouvement de la souris, en utilisant la position pour calculer un pourcentage des valeurs maximum de fréquence et de gain :
// coordonnées de la souris var positionX; var positionY; // récupère les nouvelles coordonnées de la souris quand elle bouge // puis assigne les nouvelles valeurs de gain et de pitch document.onmousemove = updatePage; function updatePage(e) { positionX = (window.Event) ? e.pageX : e.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); positionY = (window.Event) ? e.pageY : e.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); oscillateur.frequency.value = (positionX/largeur) * frequenceMax; noeudGain.gain.value = (positionY/hauteur) * volumeMax; canvasDraw(); }
Visualisation canvas simple
Une fonction canvasDraw()
est également appelée à chaque mouvement de la souris. Elle dessine une grappe de cercles à l'endroit où se trouve la souris, leur taille et couleur étant basées sur les valeurs de fréquence et de gain.
function aleatoire(number1, number2) { return number1 + (Math.floor(Math.random() * (number2 - number1)) + 1); } var canvas = document.querySelector('.canvas'); canvas.width = largeur; canvas.height = hauteur; var contexteCanvas = canvas.getContext('2d'); function canvasDraw() { rX = positionX; rY = positionY; rC = Math.floor((noeudGain.gain.value / volumeMax) * 30); canvasCtx.globalAlpha = 0.2; for(i=1;i<=15;i=i+2) { contexteCanvas.beginPath(); var chaineStyle = 'rgb(' + 100 + (i * 10) + ',' + Math.floor((noeudGain.gain.value / volumeMax) * 255); chaineStyle += ',' + Math.floor((oscillateur.frequency.value / frequenceMax) * 255) + ')'; contexteCanvas.fillStyle = chaineStyle; contexteCanvas.arc(rX + aleatoire(0, 50), rY + aleatoire(0, 50), rC / 2 + i, (Math.PI / 180) * 0, (Math.PI / 180) * 360, false); contexteCanvas.fill(); contexteCanvas.closePath(); } }
Couper le son du theremin
Quand on appuie sur le bouton pour couper le son, la fonction ci-dessous est appelée, qui déconnecte le noeud de gain du noeud de destination, cassant ainsi le graphe de façon à ce qu'aucun son ne soit produit. Appuyer de nouveau sur le bouton a l'effet inverse.
var coupeSon = document.querySelector('.mute'); coupeSon.onclick = function() { if(coupeSon.id == "") { noeudGain.disconnect(contexteAudio.destination); coupeSon.id = "activated"; coupeSon.innerHTML = "Unmute"; } else { noeudGain.connect(contexteAudio.destination); coupeSon.id = ""; coupeSon.innerHTML = "Mute"; } }
Autres options des noeuds
On peut créer un grand nombre d'autres noeuds avec la Web Audio API, et, de façon générale, ils fonctionnent de façon très similaire à ceux que nous venons de voir: on crée un noeud, le connecte avec les autres noeuds du graphe, et on manipule ensuite ses propriétés et méthodes pour agir sur la source.
Nous n'allons pas passer en revue tous les effets disponibles; vous trouverez des détails pour les utiliser sur les pages de référence de la Web_Audio_API
. Intéressons-nous simplement à quelques-unes des options.
Noeuds modulateurs d'onde
On peut créer un noeud modulatur d'onde avec la méthode AudioContext.createWaveShaper
:
var distortion = contexteAudio.createWaveShaper();
Il faut ensuite associer à cet objet une forme d'onde définie mathématiquement, qui est appliquée à l'onde de base pour créer un effet de distortion. Ces ondes ne sont pas facile à imaginer, et pour commencer le mieux est encore de chercher un algorithme sur le Web. Par exemple, nous avons trouvé celui-ci sur Stack Overflow:
function genererCourbeDistortion(amount) { var k = typeof amount === 'number' ? amount : 50, n_samples = 44100, curve = new Float32Array(n_samples), deg = Math.PI / 180, i = 0, x; for ( ; i < n_samples; ++i ) { x = i * 2 / n_samples - 1; curve[i] = ( 3 + k ) * x * 20 * deg / ( Math.PI + k * Math.abs(x) ); } return curve; };
Dans la démo Voice-change-O-matic, nous connectons le noeud distortion
au graphe audio, puis nous l'appliquons quand nous en avons besoin :
source.connect(analyser); analyser.connect(distortion); distortion.connect(biquadFilter); ... distortion.curve = genererCourbeDistortion(400);
Filtre biquad
Le filter biquad propose un certain nombre d'options; on le crée avec la méthode AudioContext.createBiquadFilter
:
var filtreBiquad = contexteAudio.createBiquadFilter();
L'option utilisée dans la démo Voice-change-o-matic est un filtre lowshelf, qui amplifie le son au niveau des basses:
filtreBiquad.type = "lowshelf"; filtreBiquad.frequency.value = 1000; filtreBiquad.gain.value = 25;
Nous spécifions le type de filtre, la fréquence, et le gain : toutes les fréquences inférieures à la valeur spécifiée verront leur gain augmenté de 25 decibels.
Autres usages de Web Audio API
La Web Audio API peut faire beaucoup d'autres choses que de la visualisation et de la spatialisation audio (par exemple régler la balance d'un son). Nous détaillerons davantage d'options dans de futurs articles, celui-ci est déjà bien assez long!