Avant l'élément <audio>
HTML5, 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 jeu modernes et certaines des tâches de mixage, de traitement et de filtrage que l'on trouve dans les applications de production audio pour ordinateur de bureau modernes. Vous trouverez ci-dessous une présentation détaillée de 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 sonores et associez-les à la destination sonore fournie par l'instance AudioContext
. Cette connexion n'a pas besoin d'être directe et peut passer par un nombre illimité de AudioNodes intermédiaires qui servent de 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
.
Sons de chargement
L'API Web Audio utilise un AudioBuffer pour les sons de courte à moyenne durée. L'approche de base consiste à utiliser XMLHttpRequest pour extraire les fichiers audio.
L'API permet de charger des 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 extrait audio:
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 être décodées ultérieurement ou décodées immédiatement à l'aide de la méthode decodeAudioData()
d'AudioContext. Cette méthode prend le ArrayBuffer
des données de fichier audio stockées dans request.response
et le décode de manière asynchrone (sans bloquer le thread d'exécution JavaScript principal).
Lorsque decodeAudioData()
est terminé, il appelle une fonction de rappel qui fournit les données audio PCM décodées en tant que AudioBuffer
.
Émission 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 planifier facilement la lecture précise du son pour les jeux et d'autres applications critiques. Toutefois, pour que cette planification fonctionne correctement, assurez-vous que vos tampons audio sont préchargés.
Appliquer une abstraction à l'API Web Audio
Bien entendu, il serait préférable de créer un système de chargement plus général qui n'est pas codé en dur pour charger ce son spécifique. Il existe de nombreuses approches pour gérer les nombreux sons courts à moyens qu'une application ou un jeu audio utiliseraient. Voici une façon d'utiliser un BufferLoader (qui ne fait pas partie de la norme Web).
Vous trouverez ci-dessous 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 avec du rythme
L'API Web Audio permet aux développeurs de planifier précisément la lecture. Pour illustrer cela, nous allons configurer une piste de rythme simple. Le schéma de batterie le plus connu est probablement le suivant:
dans lequel un charleston est joué toutes les croches, et que la grosse caisse et la caisse claire sont jouées en alternance toutes les quarts, en 4/4.
Supposons que nous ayons chargé les tampons kick
, snare
et hihat
. Le code à utiliser 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 dans 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);
}
Régler le volume d'un son
L'une des opérations les plus élémentaires que vous pouvez effectuer sur un son est de 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:
Pour configurer cette connexion, procédez 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 par programmation en manipulant gainNode.gain.value
comme suit:
// Reduce the volume.
gainNode.gain.value = 0.5;
Fondu entre deux sons
Supposons maintenant que nous ayons un scénario un peu plus complexe, dans lequel nous jouons plusieurs sons, mais que nous souhaitions les faire se fondre les uns dans les autres. Il s'agit d'un cas courant dans une application de type DJ, où nous disposons de deux platines et souhaitons pouvoir faire un panoramique d'une source sonore à une autre.
Vous pouvez le faire à l'aide du graphique audio suivant:
Pour configurer cela, nous créons simplement deux AudioGainNodes et connectons chaque source via les nœuds, à l'aide d'une fonction semblable à celle-ci:
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 avec puissance égale
Une approche naïve de fondu linéaire présente une baisse du volume lorsque vous balayez 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 plus régulier entre les régions dont le niveau peut être légèrement différent.
Mélange des titres d'une playlist
Une autre application courante du crossfader est un lecteur de musique.
Lorsque le titre change, nous voulons atténuer le titre actuel et atténuer le nouveau pour éviter une transition brutale. Pour ce faire, planifiez un fondu croisé dans le futur. Nous pourrions utiliser setTimeout
pour effectuer cette planification, mais ce n'est pas précis. Avec l'API Web Audio, nous pouvons utiliser l'interface AudioParam pour planifier des valeurs futures pour des paramètres tels que la valeur de gain d'un AudioGainNode
.
Ainsi, étant donné une playlist, nous pouvons passer d'un titre à un autre en planifiant une diminution du gain sur le titre en cours de lecture et une augmentation du gain sur le titre suivant, tous 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, comme linearRampToValueAtTime
et exponentialRampToValueAtTime
.
Bien que la fonction de temporisation de la transition puisse être sélectionnée parmi les fonctions linéaires et exponentielles intégrées (comme ci-dessus), vous pouvez également spécifier votre propre courbe de valeur 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 transmettre 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 et votre destination audio. Ce type de nœud audio peut effectuer divers filtres de bas niveau qui peuvent être utilisés pour créer des égaliseurs graphiques et même des effets plus complexes, principalement liés à la sélection des parties du spectre de fréquences d'un son à accentuer et à atténuer.
Les types de filtres compatibles sont les suivants:
- Filtre passe-bas
- Filtre passe-haut
- Filtre passe-bande
- Filtre d'étagère basse
- Filtre d'étagère haute
- Filtre de pic
- Filtre Notch
- Filtre "Toutes les cartes"
Tous les filtres incluent des paramètres permettant de spécifier un certain niveau de gain, la fréquence d'application du filtre et un facteur de qualité. Le filtre passe-bas conserve la plage de fréquences inférieures, mais élimine les fréquences élevées. Le point d'arrêt est déterminé par la valeur de la fréquence, et le facteur Q, qui n'a pas d'unité, détermine la forme du graphique. Le gain ne concerne que certains filtres, tels que les filtres à pente basse et les filtres à pic, et non ce filtre passe-bas.
Configurez un filtre passe-bas simple pour n'extraire que les basses d'un échantillon audio:
// 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 sur le même principe (c'est-à-dire que la note A4 est de 440 Hz et la note A5 de 880 Hz). Pour en savoir plus, consultez la fonction FilterSample.changeFrequency
dans le lien vers le code source ci-dessus.
Enfin, notez que l'exemple de code vous permet de connecter et de déconnecter le filtre, en modifiant dynamiquement le graphique AudioContext. Nous pouvons dissocier les AudioNodes du graphique en appelant node.disconnect(outputNumber)
.
Par exemple, pour rediriger le graphique d'un filtre vers une connexion directe, procédez comme suit:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Écouter d'autres titres
Nous avons vu 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 et des filtres de gain, ainsi que des sons programmés et des ajustements de paramètres audio pour activer certains effets sonores courants. À ce stade, vous êtes prêt à créer des applications audio Web géniales.
Si vous cherchez l'inspiration, de nombreux développeurs ont déjà créé des œuvres remarquables à l'aide de l'API Web Audio. Voici quelques-unes de mes préférées:
- AudioJedit, un outil de montage audio dans le navigateur qui utilise des liens permanents SoundCloud.
- ToneCraft, un séquenceur de sons qui crée des sons en empilant des blocs 3D.
- Plink, un jeu de création musicale collaboratif utilisant Web Audio et Web Sockets.