Prima dell'elemento HTML5 <audio>, era necessario Flash o un altro plug-in per rompere il silenzio del web. Sebbene l'audio sul web non richieda più un plug-in, il tag audio comporta limitazioni significative per l'implementazione di giochi sofisticati e applicazioni interattive.
L'API Web Audio è un'API JavaScript di alto livello per l'elaborazione e la sintesi dell'audio nelle applicazioni web. L'obiettivo di questa API è includere le funzionalità presenti nei moderni motori audio dei giochi e alcune delle attività di mixaggio, elaborazione e filtro presenti nelle moderne applicazioni di produzione audio per computer. Di seguito è riportata una breve introduzione all'utilizzo di questa potente API.
Introduzione ad AudioContext
Un AudioContext serve per gestire e riprodurre tutti i suoni. Per produrre un suono utilizzando l'API Web Audio, crea una o più sorgenti audio e collegale alla destinazione audio fornita dall'istanza AudioContext. Questa connessione non deve essere diretta e può passare attraverso un numero qualsiasi di AudioNodes intermedi che fungono da moduli di elaborazione per il segnale audio. Questo routing è descritto in maggiore dettaglio nella specifica di Web Audio.
Una singola istanza di AudioContext può supportare più input audio e grafici audio complessi, quindi ne avremo bisogno solo una per ogni applicazione audio che creiamo.
Il seguente snippet crea 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');
}
}
Per i browser meno recenti basati su WebKit, utilizza il prefisso webkit, come con webkitAudioContext.
Molte delle interessanti funzionalità dell'API Web Audio, come la creazione di AudioNode e la decodifica dei dati dei file audio, sono metodi di AudioContext.
Caricamento dei suoni in corso…
L'API Web Audio utilizza un AudioBuffer per suoni di durata breve o media. L'approccio di base consiste nell'utilizzare XMLHttpRequest per recuperare i file audio.
L'API supporta il caricamento di dati di file audio in più formati, come WAV, MP3, AAC, OGG e altri. Il supporto dei browser per i diversi formati audio varia.
Il seguente snippet mostra il caricamento di un campione 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();
}
I dati del file audio sono binari (non di testo), quindi impostiamo il responseType della richiesta su 'arraybuffer'. Per saperne di più su ArrayBuffers, consulta questo articolo su XHR2.
Una volta ricevuti i dati del file audio (non decodificati), possono essere conservati per la decodifica successiva o possono essere decodificati immediatamente utilizzando il metodo AudioContext decodeAudioData(). Questo metodo accetta i dati del file audio ArrayBuffer archiviati in request.response e li decodifica in modo asincrono (senza bloccare il thread di esecuzione JavaScript principale).
Al termine di decodeAudioData(), viene chiamata una funzione di callback che fornisce i dati audio PCM decodificati come AudioBuffer.
Riproduzione di suoni
Una volta caricati uno o più AudioBuffers, siamo pronti per riprodurre i suoni. Supponiamo di aver appena caricato un AudioBuffer con il suono di un cane che abbaia e che il caricamento sia terminato. Poi possiamo riprodurre questo buffer con il seguente codice.
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
}
Questa funzione playSound() potrebbe essere chiamata ogni volta che qualcuno preme un tasto o fa clic su un elemento con il mouse.
La funzione noteOn(time) semplifica la programmazione della riproduzione precisa dei suoni per i giochi e altre applicazioni in cui il tempo è un fattore critico. Tuttavia, per far funzionare correttamente questa programmazione, assicurati che i buffer audio siano precaricati.
Astrazione dell'API Web Audio
Naturalmente, sarebbe meglio creare un sistema di caricamento più generale che non sia hardcoded per il caricamento di questo suono specifico. Esistono molti approcci per gestire i numerosi suoni di durata breve o media che un'applicazione o un gioco audio utilizzerebbe. Ecco un modo per utilizzare un BufferLoader (non fa parte dello standard web).
Di seguito è riportato un esempio di come utilizzare la classe BufferLoader.
Creiamo due AudioBuffers e, non appena vengono caricati,
riproduciamoli contemporaneamente.
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);
}
Gestione del tempo: riproduzione di suoni con ritmo
L'API Web Audio consente agli sviluppatori di pianificare con precisione la riproduzione. Per dimostrarlo, configuriamo una semplice traccia ritmica. Probabilmente il pattern di batteria più conosciuto è il seguente:
in cui un hi-hat viene suonato ogni ottavo di nota, mentre la grancassa e il rullante vengono suonati alternandosi ogni quarto, in un tempo di 4/4.
Supponendo di aver caricato i buffer kick, snare e hihat, il codice per farlo è semplice:
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);
}
}
In questo caso, facciamo solo una ripetizione anziché il loop illimitato che vediamo nello spartito. La funzione playSound è un metodo che riproduce un buffer in un momento specificato, come segue:
function playSound(buffer, time) {
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.noteOn(time);
}
Modifica del volume di un suono
Una delle operazioni più basilari che potresti voler eseguire su un suono è modificarne il volume. Utilizzando l'API Web Audio, possiamo indirizzare la nostra sorgente alla sua destinazione tramite un AudioGainNode per manipolare il volume:

Questa configurazione della connessione può essere ottenuta nel seguente modo:
// 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);
Una volta configurato il grafico, puoi modificare il volume in modo programmatico manipolando gainNode.gain.value come segue:
// Reduce the volume.
gainNode.gain.value = 0.5;
Dissolvenza incrociata tra due suoni
Ora, supponiamo di avere uno scenario leggermente più complesso, in cui riproduciamo più suoni, ma vogliamo applicare una dissolvenza incrociata tra di loro. Questo è un caso comune in un'applicazione simile a un DJ, in cui abbiamo due giradischi e vogliamo poter eseguire il panning da una sorgente audio all'altra.
Questa operazione può essere eseguita con il seguente grafico audio:
Per configurare questa opzione, creiamo semplicemente due AudioGainNodes e colleghiamo ogni sorgente tramite i nodi, utilizzando una funzione simile a questa:
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
};
}
Crossfading a potenza costante
Un approccio di dissolvenza incrociata lineare semplice mostra un calo del volume durante il panning tra i sample.
Per risolvere questo problema, utilizziamo una curva di potenza uguale, in cui le curve di guadagno corrispondenti sono non lineari e si intersecano a un'ampiezza maggiore. In questo modo, i cali di volume tra le regioni audio vengono ridotti al minimo, ottenendo una dissolvenza incrociata più uniforme tra regioni che potrebbero avere livelli leggermente diversi.
Crossfade delle playlist
Un'altra applicazione comune del crossfader è per un'applicazione di riproduzione musicale.
Quando un brano cambia, vogliamo sfumare la traccia corrente e sfumare quella nuova per evitare una transizione brusca. Per farlo, programma una dissolvenza incrociata nel futuro. Sebbene potremmo utilizzare setTimeout per eseguire questa pianificazione, questo metodo non è preciso. Con l'API Web Audio, possiamo utilizzare l'interfaccia AudioParam per pianificare i valori futuri di parametri come il valore di guadagno di un AudioGainNode.
Pertanto, data una playlist, possiamo passare da una traccia all'altra programmando una diminuzione del guadagno sulla traccia attualmente in riproduzione e un aumento del guadagno sulla traccia successiva, entrambi leggermente prima della fine della riproduzione della traccia corrente:
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 fornisce un pratico insieme di metodi RampToValue per modificare gradualmente il valore di un parametro, ad esempio linearRampToValueAtTime e exponentialRampToValueAtTime.
Sebbene la funzione di temporizzazione della transizione possa essere scelta tra quelle lineari ed esponenziali integrate (come sopra), puoi anche specificare la tua curva di valori tramite un array di valori utilizzando la funzione setValueCurveAtTime.
Applicazione di un semplice effetto filtro a un suono

L'API Web Audio ti consente di trasferire l'audio da un nodo audio a un altro, creando una catena potenzialmente complessa di processori per aggiungere effetti complessi alle tue forme audio.
Un modo per farlo è posizionare i BiquadFilterNode tra la sorgente e la destinazione del suono. Questo tipo di nodo audio può eseguire una serie di filtri di ordine inferiore che possono essere utilizzati per creare equalizzatori grafici ed effetti ancora più complessi, principalmente per selezionare quali parti dello spettro di frequenza di un suono enfatizzare e quali attenuare.
I tipi di filtri supportati includono:
- Filtro passa-basso
- Filtro passa-alto
- Filtro passa-banda
- Filtro low shelf
- Filtro high shelf
- Filtro di peaking
- Filtro notch
- Filtro passa-tutto
Inoltre, tutti i filtri includono parametri per specificare una certa quantità di guadagno, la frequenza con cui applicare il filtro e un fattore di qualità. Il filtro passa-basso mantiene la gamma di frequenze più basse, ma scarta le frequenze alte. Il punto di interruzione è determinato dal valore della frequenza, mentre il fattore Q è senza unità e determina la forma del grafico. Il guadagno influisce solo su determinati filtri, come i filtri low-shelf e peaking, e non su questo filtro passa-basso.
Configuriamo un semplice filtro passa-basso per estrarre solo i bassi da un campione 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);
In generale, i controlli della frequenza devono essere modificati per funzionare su una scala logaritmica, poiché l'udito umano funziona secondo lo stesso principio (ovvero, A4 è 440 Hz e A5 è 880 Hz). Per maggiori dettagli, consulta la funzione FilterSample.changeFrequency nel link al codice sorgente riportato sopra.
Infine, tieni presente che il codice campione ti consente di connettere e disconnettere il filtro, modificando dinamicamente il grafico AudioContext. Possiamo disconnettere
AudioNodes dal grafico chiamando node.disconnect(outputNumber).
Ad esempio, per reindirizzare il grafico in modo che non passi attraverso un filtro, ma si connetta direttamente, possiamo procedere come segue:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Ulteriori risorse di ascolto
Abbiamo trattato le nozioni di base dell'API, tra cui il caricamento e la riproduzione di campioni audio. Abbiamo creato grafici audio con nodi di guadagno e filtri, nonché suoni programmati e modifiche ai parametri audio per abilitare alcuni effetti sonori comuni. A questo punto, è tutto pronto per creare alcune fantastiche applicazioni audio web.
Se cerchi ispirazione, molti sviluppatori hanno già creato ottimi lavori utilizzando l'API Web Audio. Alcuni dei miei preferiti includono:
- AudioJedit, uno strumento di splicing audio nel browser che utilizza i permalink di SoundCloud.
- ToneCraft, un sequencer audio in cui i suoni vengono creati impilando blocchi 3D.
- Plink, un gioco collaborativo per la creazione di musica che utilizza Web Audio e Web Sockets.