Avant l'élément HTML5 <audio>, Flash ou un autre plug-in était nécessaire pour briser le silence du Web. Bien que l'audio sur le Web ne nécessite plus de plug-in, la balise audio présente des limites importantes pour l'implémentation de jeux et d'applications interactives sophistiqués.
L'API Web Audio est une API JavaScript de haut niveau permettant de traiter et de synthétiser l'audio dans les applications Web. L'objectif de cette API est d'inclure les fonctionnalités des moteurs audio de jeux modernes, ainsi que certaines des tâches de mixage, de traitement et de filtrage que l'on trouve dans les applications de production audio de bureau modernes. Voici une introduction à l'utilisation de cette API puissante.
Premiers pas avec AudioContext
Un AudioContext permet de gérer et de lire tous les sons. Pour produire un son à l'aide de l'API Web Audio, créez une ou plusieurs sources audio et connectez-les à la destination audio fournie par l'instance AudioContext. Cette connexion n'a pas besoin d'être directe et peut passer par un nombre quelconque de nœuds AudioNodes intermédiaires qui agissent comme des modules de traitement du signal audio. Ce routage est décrit plus en détail dans la spécification Web Audio.
Une seule instance de AudioContext peut prendre en charge plusieurs entrées audio et des graphiques audio complexes. Nous n'en aurons donc besoin que d'une seule pour chaque application audio que nous créons.
L'extrait de code suivant crée un AudioContext :
var context;
window.addEventListener('load', init, false);
function init() {
try {
context = new AudioContext();
}
catch(e) {
alert('Web Audio API is not supported in this browser');
}
}
Pour les anciens navigateurs basés sur WebKit, utilisez le préfixe webkit, comme avec webkitAudioContext.
De nombreuses fonctionnalités intéressantes de l'API Web Audio, telles que la création d'AudioNodes et le décodage des données de fichiers audio, sont des méthodes de AudioContext.
Chargement des sons…
L'API Web Audio utilise un AudioBuffer pour les sons de courte à moyenne durée. L'approche de base consiste à utiliser XMLHttpRequest pour récupérer les fichiers audio.
L'API accepte le chargement de données de fichiers audio dans plusieurs formats, tels que WAV, MP3, AAC, OGG et autres. La compatibilité des navigateurs avec les différents formats audio varie.
L'extrait de code suivant montre comment charger un échantillon sonore :
var dogBarkingBuffer = null;
var context = new AudioContext();
function loadDogSound(url) {
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
// Decode asynchronously
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
dogBarkingBuffer = buffer;
}, onError);
}
request.send();
}
Les données du fichier audio sont binaires (et non textuelles). Nous définissons donc le responseType de la requête sur 'arraybuffer'. Pour en savoir plus sur ArrayBuffers, consultez cet article sur XHR2.
Une fois les données du fichier audio (non décodées) reçues, elles peuvent être conservées pour un décodage ultérieur ou être décodées immédiatement à l'aide de la méthode AudioContext decodeAudioData(). Cette méthode prend l'ArrayBuffer des données du fichier audio stockées dans request.response et les décode de manière asynchrone (sans bloquer le thread d'exécution JavaScript principal).
Une fois l'opération terminée, decodeAudioData() appelle une fonction de rappel qui fournit les données audio PCM décodées sous la forme d'un AudioBuffer.
Lecture de sons…
Une fois qu'un ou plusieurs AudioBuffers sont chargés, nous sommes prêts à lire des sons. Supposons que nous venons de charger un AudioBuffer avec le son d'un chien qui aboie et que le chargement est terminé. Nous pouvons ensuite lire ce tampon avec le code suivant.
var context = new AudioContext();
function playSound(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer; // tell the source which sound to play
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.noteOn(0); // play the source now
}
Cette fonction playSound() peut être appelée chaque fois qu'un utilisateur appuie sur une touche ou clique sur un élément avec la souris.
La fonction noteOn(time) permet de programmer facilement la lecture précise de sons pour les jeux et d'autres applications urgentes. Toutefois, pour que cette programmation fonctionne correctement, assurez-vous que vos tampons audio sont préchargés.
Appliquer une abstraction à l'API Web Audio
Bien sûr, il serait préférable de créer un système de chargement plus général qui ne soit pas codé en dur pour charger ce son spécifique. Il existe de nombreuses approches pour gérer les nombreux sons de courte à moyenne durée qu'une application ou un jeu audio utiliserait. En voici une qui utilise un BufferLoader (ne fait pas partie de la norme Web).
Voici un exemple d'utilisation de la classe BufferLoader.
Créons deux AudioBuffers et, dès qu'ils sont chargés, lisons-les en même temps.
window.onload = init;
var context;
var bufferLoader;
function init() {
context = new AudioContext();
bufferLoader = new BufferLoader(
context,
[
'../sounds/hyper-reality/br-jam-loop.wav',
'../sounds/hyper-reality/laughter.wav',
],
finishedLoading
);
bufferLoader.load();
}
function finishedLoading(bufferList) {
// Create two sources and play them both together.
var source1 = context.createBufferSource();
var source2 = context.createBufferSource();
source1.buffer = bufferList[0];
source2.buffer = bufferList[1];
source1.connect(context.destination);
source2.connect(context.destination);
source1.noteOn(0);
source2.noteOn(0);
}
Gérer le temps : jouer des sons en rythme
L'API Web Audio permet aux développeurs de programmer précisément la lecture. Pour le démontrer, configurons une piste rythmique simple. Le motif de batterie le plus connu est probablement le suivant :
dans lequel une charleston est jouée toutes les croches, et la grosse caisse et la caisse claire sont jouées en alternance toutes les noires, en mesure 4/4.
En supposant que nous ayons chargé les tampons kick, snare et hihat, le code pour effectuer cette opération est simple :
for (var bar = 0; bar < 2; bar++) {
var time = startTime + bar * 8 * eighthNoteTime;
// Play the bass (kick) drum on beats 1, 5
playSound(kick, time);
playSound(kick, time + 4 * eighthNoteTime);
// Play the snare drum on beats 3, 7
playSound(snare, time + 2 * eighthNoteTime);
playSound(snare, time + 6 * eighthNoteTime);
// Play the hi-hat every eighth note.
for (var i = 0; i < 8; ++i) {
playSound(hihat, time + i * eighthNoteTime);
}
}
Ici, nous ne faisons qu'une seule répétition au lieu de la boucle illimitée que nous voyons sur la partition. La fonction playSound est une méthode qui lit un tampon à un moment spécifié, comme suit :
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
Modifier le volume d'un son
L'une des opérations les plus simples que vous pouvez effectuer sur un son consiste à modifier son volume. À l'aide de l'API Web Audio, nous pouvons acheminer notre source vers sa destination via un AudioGainNode afin de manipuler le volume :

Cette configuration de connexion peut être effectuée comme suit :
// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);
Une fois le graphique configuré, vous pouvez modifier le volume de manière programmatique en manipulant le gainNode.gain.value comme suit :
// Reduce the volume.
gainNode.gain.value = 0.5;
Fondu enchaîné entre deux sons
Supposons maintenant que nous ayons un scénario un peu plus complexe, dans lequel nous lisons plusieurs sons, mais que nous voulons effectuer un fondu enchaîné entre eux. C'est un cas courant dans une application de type DJ, où nous avons deux platines et voulons pouvoir passer d'une source sonore à une autre.
Pour ce faire, vous pouvez utiliser le graphique audio suivant :
Pour ce faire, il suffit de créer deux AudioGainNodes et de connecter chaque source via les nœuds, en utilisant une fonction de ce type :
function createSource(buffer) {
var source = context.createBufferSource();
// Create a gain node.
var gainNode = context.createGainNode();
source.buffer = buffer;
// Turn on looping.
source.loop = true;
// Connect source to gain.
source.connect(gainNode);
// Connect gain to destination.
gainNode.connect(context.destination);
return {
source: source,
gainNode: gainNode
};
}
Fondu enchaîné à puissance égale
Une approche de fondu enchaîné linéaire naïve présente une baisse de volume lorsque vous effectuez un panoramique entre les échantillons.
Pour résoudre ce problème, nous utilisons une courbe de puissance égale, dans laquelle les courbes de gain correspondantes sont non linéaires et se croisent à une amplitude plus élevée. Cela réduit les baisses de volume entre les régions audio, ce qui permet d'obtenir un fondu enchaîné plus uniforme entre les régions dont le niveau peut être légèrement différent.
Fondu enchaîné des playlists
Une autre application courante du fondu enchaîné est un lecteur de musique.
Lorsqu'un titre change, nous voulons que le titre actuel s'estompe et que le nouveau apparaisse progressivement, afin d'éviter une transition brutale. Pour ce faire, planifiez un fondu enchaîné dans le futur. Bien que nous puissions utiliser setTimeout pour effectuer cette planification, ce n'est pas précis. Avec l'API Web Audio, nous pouvons utiliser l'interface AudioParam pour planifier les futures valeurs de paramètres tels que la valeur de gain d'un AudioGainNode.
Ainsi, pour une playlist donnée, nous pouvons passer d'un titre à l'autre en planifiant une diminution du gain sur le titre en cours de lecture et une augmentation du gain sur le titre suivant, toutes deux légèrement avant la fin de la lecture du titre en cours :
function playHelper(bufferNow, bufferLater) {
var playNow = createSource(bufferNow);
var source = playNow.source;
var gainNode = playNow.gainNode;
var duration = bufferNow.duration;
var currTime = context.currentTime;
// Fade the playNow track in.
gainNode.gain.linearRampToValueAtTime(0, currTime);
gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
// Play the playNow track.
source.noteOn(0);
// At the end of the track, fade it out.
gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
// Schedule a recursive track change with the tracks swapped.
var recurse = arguments.callee;
ctx.timer = setTimeout(function() {
recurse(bufferLater, bufferNow);
}, (duration - ctx.FADE_TIME) - 1000);
}
L'API Web Audio fournit un ensemble pratique de méthodes RampToValue pour modifier progressivement la valeur d'un paramètre, telles que linearRampToValueAtTime et exponentialRampToValueAtTime.
Bien que la fonction de minutage de la transition puisse être choisie parmi les fonctions linéaires et exponentielles intégrées (comme ci-dessus), vous pouvez également spécifier votre propre courbe de valeurs via un tableau de valeurs à l'aide de la fonction setValueCurveAtTime.
Appliquer un effet de filtre simple à un son

L'API Web Audio vous permet de transférer le son d'un nœud audio à un autre, créant ainsi une chaîne de processeurs potentiellement complexe pour ajouter des effets complexes à vos formes sonores.
Pour ce faire, vous pouvez placer des BiquadFilterNode entre votre source sonore et votre destination. Ce type de nœud audio peut effectuer divers filtres d'ordre inférieur qui peuvent être utilisés pour créer des égaliseurs graphiques et des effets encore plus complexes, principalement pour sélectionner les parties du spectre de fréquences d'un son à accentuer et celles à atténuer.
Les types de filtres compatibles sont les suivants :
- Filtre passe-bas
- Filtre passe-haut
- Filtre passe-bande
- Filtre Low Shelf
- Filtre de plateau haut
- Filtre de crête
- Filtre coupe-bande
- Filtre passe-tout
Tous les filtres incluent des paramètres permettant de spécifier une certaine quantité de gain, la fréquence à laquelle appliquer le filtre et un facteur de qualité. Le filtre passe-bas conserve la plage de fréquences inférieure, mais supprime les hautes fréquences. Le point de coupure est déterminé par la valeur de fréquence, et le facteur Q est sans unité et détermine la forme du graphique. Le gain n'affecte que certains filtres, tels que les filtres low-shelf et peaking, et non ce filtre passe-bas.
Configurons un filtre passe-bas simple pour extraire uniquement les basses d'un échantillon sonore :
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);
En général, les commandes de fréquence doivent être ajustées pour fonctionner sur une échelle logarithmique, car l'audition humaine fonctionne elle-même sur le même principe (c'est-à-dire que A4 est de 440 Hz et A5 de 880 Hz). Pour en savoir plus, consultez la fonction FilterSample.changeFrequency dans le lien du code source ci-dessus.
Enfin, notez que l'exemple de code vous permet de connecter et de déconnecter le filtre, ce qui modifie dynamiquement le graphique AudioContext. Nous pouvons déconnecter les AudioNodes du graphique en appelant node.disconnect(outputNumber).
Par exemple, pour rediriger le graphique d'un filtre vers une connexion directe, nous pouvons procéder comme suit :
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Écoute prolongée
Nous avons abordé les principes de base de l'API, y compris le chargement et la lecture d'échantillons audio. Nous avons créé des graphiques audio avec des nœuds de gain et des filtres, et nous avons programmé des sons et des ajustements de paramètres audio pour activer certains effets sonores courants. Vous êtes maintenant prêt à créer de superbes applications audio Web.
Si vous cherchez de l'inspiration, de nombreux développeurs ont déjà créé d'excellents projets à l'aide de l'API Web Audio. Voici quelques-uns de mes préférés :
- AudioJedit, un outil de montage audio intégré au navigateur qui utilise des liens permanents SoundCloud.
- ToneCraft, un séquenceur audio dans lequel les sons sont créés en empilant des blocs 3D.
- Plink, un jeu de création musicale collaborative utilisant Web Audio et Web Sockets.