Prima dell'elemento <audio>
HTML5, 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 presenta 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 sintetizzazione dell'audio nelle applicazioni web. L'obiettivo di questa API è includere le funzionalità presenti nei moderni motori di gioco audio e alcune attività di missaggio, elaborazione e filtro presenti nelle moderne applicazioni di produzione audio per desktop. Di seguito trovi un'introduzione gentile all'uso di questa potente API.
Introduzione ad AudioContext
Un AudioContext consente di 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 più dettagliatamente nella specifica dell'audio web.
Una singola istanza di AudioContext
può supportare più ingressi audio
e grafici audio complessi, quindi ne avremo bisogno solo per ogni
applicazione audio creata.
Lo snippet seguente 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 basati su WebKit meno recenti, utilizza il prefisso webkit
, come
webkitAudioContext
.
Molte delle interessanti funzionalità dell'API Web Audio, come la creazione di
AudioNodes e la decodifica dei dati dei file audio, sono metodi di AudioContext
.
Caricamento suoni in corso...
L'API Web Audio utilizza un AudioBuffer per i suoni di durata breve e media. L'approccio di base consiste nell'utilizzare XMLHttpRequest per il recupero dei file audio.
L'API supporta il caricamento di dati di file audio in più formati, ad esempio WAV, MP3, AAC, OGG e altri. Il supporto dei browser per diversi formati audio varia.
Lo snippet seguente mostra il caricamento di un esempio 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 maggiori informazioni su ArrayBuffers
, consulta questo articolo su XHR2.
Una volta ricevuti, i dati del file audio (non decodificati) possono essere tenuti a disposizione per una successiva decodifica oppure possono essere decodificati immediatamente utilizzando il metodo decodeAudioData()
AudioContext. Questo metodo prende l'ArrayBuffer
di dati dei file audio archiviati in request.response
e li decodifica in modo asincrono (senza bloccare il thread di esecuzione JavaScript principale).
Al termine, decodeAudioData()
chiama una funzione di callback che fornisce i dati audio PCM decodificati come AudioBuffer
.
Riproduzione di suoni in corso...
Una volta caricati uno o più AudioBuffers
, potrai iniziare a riprodurre i suoni. Supponiamo di aver appena caricato un AudioBuffer
con il suono
di un cane che abbaia e che il caricamento sia terminato. Possiamo quindi 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()
può essere chiamata ogni volta che qualcuno preme un tasto o
fa clic su qualcosa con il mouse.
La funzione noteOn(time)
semplifica la pianificazione di una riproduzione audio precisa per giochi e altre applicazioni critiche. Tuttavia, per far sì che
questa pianificazione funzioni correttamente, assicurati che i buffer audio siano precaricati.
Astrattismo dell'API Web Audio
Ovviamente sarebbe meglio creare un sistema di caricamento più generale, che non sia hardcoded in modo da caricare l'audio in questione. Esistono diversi approcci per gestire i numerosi suoni di breve e media durata che un'applicazione o un gioco audio potrebbe utilizzare. Ecco un modo per utilizzare BufferLoader (non fa parte dello standard web).
Di seguito è riportato un esempio di come puoi utilizzare la classe BufferLoader
.
Creiamo due AudioBuffers
e, appena vengono caricati,
rieseguiamo la riproduzione 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);
}
Affrontare il tempo: riprodurre i suoni a ritmo
L'API Web Audio consente agli sviluppatori di programmare con precisione la riproduzione. Per dimostrarlo, impostiamo una semplice traccia ritmica. Probabilmente il pattern di batteria più noto è il seguente:
in cui si suona un hihat ogni ottava, mentre calcio e 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 eseguire questa operazione è 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);
}
}
Qui facciamo una sola ripetizione invece del loop illimitato che si vede
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);
}
Regolare il volume di un suono
Una delle operazioni di base che si potrebbe eseguire per un suono è regolarne il volume. Utilizzando l'API Web Audio, possiamo instradare la sorgente alla sua destinazione tramite un AudioGainNode, in modo da manipolare il volume:
Questa configurazione della connessione può essere effettuata come segue:
// 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);
Dopo aver configurato il grafico, puoi modificare il volume
a livello di programmazione 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 eproduciamo più suoni, ma vogliamo applicare una dissolvenza incrociata. Si tratta di un caso comune in un'applicazione simile a DJ, in cui abbiamo due giradischi e vogliamo essere in grado di eseguire la panoramica da una sorgente all'altra.
Per farlo, usa il seguente grafico audio:
Per configurarlo, creiamo semplicemente due AudioGainNodes e connettiamo ogni origine tramite i nodi utilizzando una funzione simile alla seguente:
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
};
}
Dissolvenza incrociata a potenza uguale
Un approccio ingenuo alla dissolvenza incrociata lineare mostra un calo di volume durante la panoramica tra i campioni.
Per risolvere questo problema, utilizziamo una curva di potenza uguale, in cui le curve di guadagno corrispondenti non sono lineari e si intersecano con un'ampiezza maggiore. Questo riduce al minimo i cali di volume tra le regioni audio, con una conseguente dissolvenza incrociata più uniforme tra le regioni il cui livello potrebbe essere leggermente diverso.
Dissolvenza incrociata delle playlist
Un'altra applicazione crossfader comune è quella di un lettore musicale.
Quando un brano cambia, vogliamo eliminare la traccia corrente
e aggiungere una dissolvenza a quella nuova in entrata, per evitare una transizione fastidiosa. Per farlo, pianifica una
dissolvenza incrociata futura. Anche se potremmo utilizzare setTimeout
per questa
pianificazione, questa programmazione non è precisa. Con l'API Web Audio, possiamo utilizzare l'interfaccia AudioParam per pianificare valori futuri di
parametri come il valore di guadagno di AudioGainNode
.
Di conseguenza, data una playlist, possiamo passare da una traccia all'altra programmando una diminuzione della traccia attualmente in riproduzione e un aumento del guadagno su quella successiva, entrambi leggermente prima della fine della riproduzione della traccia attuale:
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 offre un comodo 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 integrate lineari ed esponenziali (come sopra), puoi anche specificare la tua curva di valore tramite un array di valori utilizzando la funzione setValueCurveAtTime
.
Applicare un semplice effetto di filtro a un suono
L'API Web Audio consente di trasferire il suono da un nodo audio a un altro, creando una catena potenzialmente complessa di processori per aggiungere effetti complessi alle forme sonore.
Un modo per farlo è posizionare BiquadFilterNode tra l'origine e la destinazione audio. Questo tipo di nodo audio può disporre di una varietà di filtri di ordine basso che possono essere utilizzati per creare equalizzatori grafici ed effetti ancora più complessi, principalmente per selezionare le parti dello spettro di frequenza di un suono da enfatizzare e quali abbassare.
I tipi di filtri supportati includono:
- Filtro passa basso
- Filtro passa alto
- Filtro Passa banda
- Filtro scaffale basso
- Filtro scaffale alto
- Filtro Peaking
- Filtro tacca
- Filtro di tutte le tessere
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 l'intervallo di frequenza più basso, ma scarta le frequenze alte. Il punto di interruzione è determinato dal valore della frequenza e il fattore Q è senza unità e determina la forma del grafico. Il guadagno interessa solo alcuni filtri, come quelli di fascia bassa e di picco, e non su questo filtro passa-basso.
Configuriamo un semplice filtro passa-basso per estrarre solo le basi da un campione sonoro:
// 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 regolati per funzionare su una scala logaritmica, poiché l'udito umano stesso funziona sullo stesso principio (ovvero, A4 è 440 Hz e A5 è 880 Hz). Per maggiori dettagli, consulta la funzione FilterSample.changeFrequency
nel link del codice sorgente sopra.
Infine, tieni presente che il codice campione consente di collegare e scollegare il filtro, modificando in modo dinamico il grafico AudioContext. Possiamo scollegare
AudioNodes dal grafico chiamando node.disconnect(outputNumber)
.
Ad esempio, per reindirizzare il grafico da un filtro a una
connessione diretta, possiamo fare quanto segue:
// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);
Ascolto ulteriore
Abbiamo esaminato le nozioni di base dell'API, inclusi il caricamento e la riproduzione di esempi audio. Abbiamo creato grafici audio con nodi e filtri di guadagno, suoni programmati e modifiche ai parametri audio per abilitare alcuni effetti sonori comuni. A questo punto, sei pronto per creare delle dolci applicazioni audio web.
Se cerchi ispirazione, molti sviluppatori hanno già realizzato ottimo lavoro utilizzando l'API Web Audio. Ecco alcuni dei miei preferiti:
- AudioJedit, uno strumento di giunzione del suono integrato nel browser che utilizza i permalink di SoundCloud.
- ToneCraft, un sequenziatore di suoni in cui i suoni vengono creati impilando blocchi 3D.
- Plink, un gioco collaborativo per la creazione di musica che utilizza Web Audio e Web Socket.