Introduzione
L'audio è un elemento fondamentale che rende così coinvolgenti le esperienze multimediali. Se ti è mai capitato di provare a guardare un film con l'audio disattivato, l'avrai notato.
I giochi non fanno eccezione. I miei ricordi più belli dei videogiochi riguardano la musica e gli effetti sonori. Ora, in molti casi, quasi vent'anni dopo aver giocato ai miei titoli preferiti, non riesco ancora a togliermi dalla testa le composizioni di Koji Kondo per Zelda e la colonna sonora di Matt Uelmen per Diablo. La stessa orecchiabilità si applica agli effetti sonori, come le risposte ai clic delle unità immediatamente riconoscibili di Warcraft e i sample dei classici di Nintendo.
L'audio dei giochi presenta alcune sfide interessanti. Per creare musica convincente per i giochi, i designer devono adattarsi allo stato di gioco potenzialmente imprevedibile in cui si trova un giocatore. In pratica, alcune parti del gioco possono durare per una durata sconosciuta, i suoni possono interagire con l'ambiente e mescolarsi in modi complessi, ad esempio con effetti ambientali e posizionamento relativo dei suoni. Infine, può essere riprodotto contemporaneamente un numero elevato di suoni, che devono suonare bene insieme e essere visualizzati senza introdurre penalizzazioni delle prestazioni.
Audio dei giochi sul web
Per i giochi semplici, l'utilizzo del tag <audio>
potrebbe essere sufficiente. Tuttavia, molti browser offrono implementazioni scadenti, che si traducono in glitch audio e latenza elevata. Si spera che si tratti di un problema temporaneo, poiché i fornitori si stanno adoperando per migliorare le rispettive implementazioni. Per avere un'idea dello stato del tag <audio>
, puoi trovare una suite di test interessante all'indirizzo areweplayingyet.org.
Tuttavia, esaminando più da vicino la specifica del tag <audio>
, diventa chiaro che ci sono molte cose che semplicemente non possono essere fatte con questo tag, il che non sorprende, dato che è stato progettato per la riproduzione di contenuti multimediali. Ecco alcune limitazioni:
- Impossibilità di applicare filtri al segnale audio
- Nessun modo per accedere ai dati PCM non elaborati
- Nessun concetto di posizione e direzione di sorgenti e ascoltatori
- Nessuna tempistica definita.
Nel resto dell'articolo, esamino alcuni di questi argomenti nel contesto dell'audio di gioco scritto con l'API Web Audio. Per una breve introduzione a questa API, consulta il tutorial introduttivo.
Musica di sottofondo
I giochi spesso hanno musica di sottofondo riprodotta in loop.
Può diventare molto fastidioso se il loop è breve e prevedibile. Se un giocatore rimane bloccato in un'area o in un livello e lo stesso sample viene riprodotto continuamente in background, potrebbe essere utile attenuare gradualmente la traccia per evitare ulteriori frustrazioni. Un'altra strategia è avere mix di varie intensità che si fondono gradualmente l'uno nell'altro, a seconda del contesto del gioco.
Ad esempio, se il giocatore si trova in una zona con un'epica battaglia con un boss, potresti avere diversi mix che variano nell'intervallo emotivo da atmosferico a premonitore a intenso. I software di sintesi musicale spesso ti consentono di esportare diversi mix (della stessa durata) in base a un brano scegliendo l'insieme di tracce da utilizzare nell'esportazione. In questo modo avrai una certa coerenza interna ed eviterai transizioni sgradevoli durante il passaggio da una traccia all'altra.
Quindi, con l'API Web Audio, puoi importare tutti questi campioni utilizzando, ad esempio, la classe BufferLoader tramite XHR (trattata approfonditamente nell'articolo introduttivo sull'API Web Audio). Il caricamento dei suoni richiede tempo, quindi gli asset utilizzati nel gioco devono essere caricati al caricamento della pagina, all'inizio del livello o, eventualmente, in modo incrementale mentre il giocatore gioca.
Successivamente, creerai un'origine per ogni nodo e un nodo di guadagno per ogni origine e collegherai il grafico.
Dopodiché, puoi riprodurre tutte queste origini contemporaneamente in loop e, poiché hanno tutte la stessa durata, l'API Web Audio garantirà che rimarranno allineate. Man mano che il personaggio si avvicina o si allontana dal combattimento con il boss finale, il gioco può variare i valori di guadagno per ciascuno dei rispettivi nodi della catena, utilizzando un algoritmo di importo del guadagno come il seguente:
// Assume gains is an array of AudioGainNode, normVal is the intensity
// between 0 and 1.
var value = normVal - (gains.length - 1);
// First reset gains on all nodes.
for (var i = 0; i < gains.length; i++) {
gains[i].gain.value = 0;
}
// Decide which two nodes we are currently between, and do an equal
// power crossfade between them.
var leftNode = Math.floor(value);
// Normalize the value between 0 and 1.
var x = value - leftNode;
var gain1 = Math.cos(x - 0.5*Math.PI);
var gain2 = Math.cos((1.0 - x) - 0.5*Math.PI);
// Set the two gains accordingly.
gains[leftNode].gain.value = gain1;
// Check to make sure that there's a right node.
if (leftNode < gains.length - 1) {
// If there is, adjust its gain.
gains[leftNode + 1].gain.value = gain2;
}
Nell'approccio sopra descritto, vengono riprodotte contemporaneamente due sorgenti e applichiamo la transizione graduale tra di loro utilizzando curve di potenza uguali (come descritto nell'introduzione).
Il link mancante: tag audio a Web Audio
Oggi molti sviluppatori di giochi utilizzano il tag <audio>
per la musica di sottofondo, in quanto è molto adatto per lo streaming di contenuti. Ora puoi inserire contenuti del tag <audio>
in un contesto Web Audio.
Questa tecnica può essere utile perché il tag <audio>
può funzionare con
i contenuti in streaming, consentendo di riprodurre immediatamente la musica di sottofondo
invece di dover attendere il download completo. Inserendo lo stream nell'API Web Audio, puoi manipolarlo o analizzarlo. Il
seguente esempio applica un filtro passa basso alla musica riprodotta tramite
il tag <audio>
:
var audioElement = document.querySelector('audio');
var mediaSourceNode = context.createMediaElementSource(audioElement);
// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
mediaSourceNode.connect(filter);
filter.connect(context.destination);
Per una discussione più completa sull'integrazione del tag <audio>
con l'API Web Audio, consulta questo breve articolo.
Effetti sonori
I giochi spesso riproducono effetti sonori in risposta all'input dell'utente o alle modifiche allo stato del gioco. Tuttavia, come la musica di sottofondo, gli effetti sonori possono diventare molto fastidiosi molto rapidamente. Per evitare questo, spesso è utile avere a disposizione un insieme di suoni simili, ma diversi, da riprodurre. Possono variare da leggere variazioni di campioni di passi a variazioni drastiche, come si vede nella serie Warcraft in risposta al clic sulle unità.
Un'altra caratteristica fondamentale degli effetti sonori nei giochi è che possono essere presenti contemporaneamente. Immagina di trovarti nel bel mezzo di uno scontro a fuoco con più attori che sparano con mitragliatrici. Ogni mitragliatrice spara molte volte al secondo, causando la riproduzione di decine di effetti sonori contemporaneamente. La riproduzione simultanea di audio da più sorgenti temporizzate in modo preciso è il punto di forza dell'API Web Audio.
L'esempio seguente crea un round di mitragliatrice a partire da più campioni di punti singoli creando più sorgenti audio la cui riproduzione è sfalsata nel tempo.
var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
var source = this.makeSource(this.buffers[M4A1]);
source.noteOn(time + i - interval);
}
Ora, se tutte le mitragliatrici del tuo gioco suonassero esattamente così, sarebbe piuttosto noioso. Ovviamente variano anche per suono in base alla distanza dall'obiettivo e alla posizione relativa (ulteriori informazioni in seguito), ma anche questo potrebbe non bastare. Fortunatamente, l'API Web Audio offre un modo per modificare facilmente l'esempio riportato sopra in due modi:
- Con un leggero spostamento tra i vari punti dello sparo
- Modificando la velocità di riproduzione di ogni sample (modificando anche l'intonazione) per simulare meglio la casualità del mondo reale.
Per un esempio più realistico di queste tecniche in azione, dai un'occhiata alla demo del tavolo da biliardo, che utilizza il campionamento casuale e varia la frequenza di riproduzione per un suono di collisione delle palle più interessante.
Audio posizionale 3D
I giochi sono spesso ambientati in un mondo con alcune proprietà geometriche, in 2D o in 3D. In questo caso, l'audio in stereo può aumentare notevolmente l'esperienza immersiva. Fortunatamente, l'API Web Audio offre funzionalità audio posizionali con accelerazione hardware integrate che sono abbastanza semplici da utilizzare. A proposito, per comprendere l'esempio che segue, devi assicurarti di avere altoparlanti stereo (preferibilmente cuffie).
Nell'esempio precedente, al centro del canvas è presente un ascoltatore (icona di una persona) e il mouse influisce sulla posizione della sorgente (icona di un altoparlante). Quello riportato sopra è un semplice esempio di utilizzo di AudioPannerNode per ottenere questo tipo di effetto. L'idea di base dell'esempio riportato sopra è rispondere al movimento del mouse impostando la posizione dell'origine audio come segue:
PositionSample.prototype.changePosition = function(position) {
// Position coordinates are in normalized canvas coordinates
// with -0.5 < x, y < 0.5
if (position) {
if (!this.isPlaying) {
this.play();
}
var mul = 2;
var x = position.x / this.size.width;
var y = -position.y / this.size.height;
this.panner.setPosition(x - mul, y - mul, -0.5);
} else {
this.stop();
}
};
Cose da sapere sul trattamento della spazializzazione da parte dell'audio web:
- Per impostazione predefinita, l'ascoltatore si trova nell'origine (0, 0, 0).
- Le API di posizionamento Web Audio sono senza unità di misura, quindi ho introdotto un moltiplicatore per migliorare il suono della demo.
- L'audio web utilizza le coordinate cartesiche y-is-up (l'opposto della maggior parte dei sistemi grafici informatici). Per questo motivo sto scambiando l'asse Y nello snippet sopra
Avanzate: coni acustici
Il modello posizionale è molto potente e abbastanza avanzato, in gran parte basato su OpenAL. Per ulteriori dettagli, consulta le sezioni 3 e 4 della specifica collegata sopra.
È presente un singolo AudioListener collegato al contesto dell'API Web Audio, che può essere configurato nello spazio tramite posizione e orientamento. Ogni sorgente può essere passata attraverso un AudioPannerNode, che spazializza l'audio di input. Il nodo di panoramica ha posizione e orientamento, nonché un modello di distanza e direzione.
Il modello di distanza specifica la quantità di guadagno in base alla vicinanza alla sorgente, mentre il modello direzionale può essere configurato specificando un cono interno ed esterno, che determinano la quantità di guadagno (in genere negativo) se l'ascoltatore si trova all'interno del cono interno, tra i coni interno ed esterno o all'esterno del cono esterno.
var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;
Anche se il mio esempio è in 2D, questo modello si generalizza facilmente alla terza dimensione. Per un esempio di audio spazializzato in 3D, guarda questo esempio posizionale. Oltre alla posizione, il modello audio di Web Audio include facoltativamente anche la velocità per gli spostamenti doppler. Questo esempio mostra l'effetto doppler in modo più dettagliato.
Per saperne di più su questo argomento, leggi questo tutorial dettagliato su [mixing positional audio and WebGL][webgl].
Effetti e filtri della stanza
In realtà, il modo in cui il suono viene percepito dipende molto dalla stanza in cui viene ascoltato. La stessa porta che scricchiola avrà un suono molto diverso in un seminterrato rispetto a un grande salone aperto. I giochi con un elevato valore di produzione vorranno imitare questi effetti, poiché la creazione di un insieme distinto di sample per ogni ambiente è proibitivamente costosa e comporterebbe un numero ancora maggiore di asset e una quantità maggiore di dati di gioco.
In senso lato, il termine audio per la differenza tra il suono grezzo e il modo in cui suona nella realtà è la risposta all'impulso. Queste risposte all'impulso possono essere registrate con estrema difficoltà e, infatti, esistono siti che ospitano molti di questi file di risposta all'impulso preregistrati (memorizzati come audio) per tua comodità.
Per ulteriori informazioni su come creare le risposte all'impulso da un determinato ambiente, consulta la sezione "Configurazione della registrazione" nella parte relativa alla convoluzione della specifica dell'API Web Audio.
Ancora più importante per i nostri scopi, l'API Web Audio fornisce un modo semplice per applicare queste risposte all'impulso ai nostri suoni utilizzando ConvolverNode.
// Make a source node for the sample.
var source = context.createBufferSource();
source.buffer = this.buffer;
// Make a convolver node for the impulse response.
var convolver = context.createConvolver();
convolver.buffer = this.impulseResponseBuffer;
// Connect the graph.
source.connect(convolver);
convolver.connect(context.destination);
Guarda anche questa demo degli effetti di ambiente nella pagina delle specifiche dell'API Web Audio, nonché questo esempio che ti consente di controllare il mixaggio asciutto (non elaborato) e bagnato (elaborato tramite convolver) di un grande standard jazz.
Il conto alla rovescia finale
Hai quindi creato un gioco, configurato l'audio posizionale e ora nel grafico sono presenti numerosi AudioNodi, tutti in riproduzione simultaneamente. Ottimo, ma c'è ancora un'altra cosa da considerare:
Poiché più suoni si sovrappongono senza essere normalizzati, potresti trovarti in una situazione in cui superi la soglia di capacità dello speaker. Come nel caso delle immagini che superano i margini dell'area di lavoro, i suoni possono anche tagliarsi se la forma d'onda supera la soglia massima, producendo una netta distorsione. L'onda ha il seguente aspetto:
Ecco un esempio reale di clipping in azione. La forma d'onda non è buona:
È importante ascoltare distorsioni forti come quella sopra o, al contrario, mix troppo attenuati che costringono gli ascoltatori ad alzare il volume. Se ti trovi in questa situazione, devi assolutamente risolvere il problema.
Rilevare il ritaglio
Da un punto di vista tecnico, il ritaglio si verifica quando il valore dell'indicatore in un canale supera l'intervallo valido, ovvero compreso tra -1 e 1. Una volta rilevato, è utile fornire un feedback visivo che lo confermi. Per farlo in modo affidabile, inserisci un JavaScriptAudioNode nel grafico. Il grafico audio viene configurato nel seguente modo:
// Assume entire sound output is being piped through the mix node.
var meter = context.createJavaScriptNode(2048, 1, 1);
meter.onaudioprocess = processAudio;
mix.connect(meter);
meter.connect(context.destination);
Inoltre, il clipping potrebbe essere rilevato nel seguente gestore processAudio
:
function processAudio(e) {
var buffer = e.inputBuffer.getChannelData(0);
var isClipping = false;
// Iterate through buffer to check if any of the |values| exceeds 1.
for (var i = 0; i < buffer.length; i++) {
var absValue = Math.abs(buffer[i]);
if (absValue >= 1) {
isClipping = true;
break;
}
}
}
In generale, fai attenzione a non utilizzare eccessivamente JavaScriptAudioNode
per motivi di rendimento. In questo caso, un'implementazione alternativa del monitoraggio potrebbe includere un RealtimeAnalyserNode
nel grafico audio per getByteFrequencyData
, al momento del rendering, come stabilito da requestAnimationFrame
. Questo approccio è più efficiente, ma perde gran parte del segnale (inclusi i punti in cui potrebbe verificarsi clipping), poiché il rendering avviene al massimo 60 volte al secondo, mentre il segnale audio cambia molto più rapidamente.
Poiché il rilevamento dei clip è così importante, è probabile che in futuro vedremo un node MeterNode
Web Audio API integrato.
Evitare il ritaglio
Regolando il guadagno su AudioGainNode principale, puoi attenuare il mix a un livello che impedisca il clipping. Tuttavia, in pratica, poiché i suoni riprodotti nel gioco possono dipendere da una vasta gamma di fattori, può essere difficile decidere il valore del guadagno principale che impedisca il clipping per tutti gli stati. In generale, è bene apportare piccole modifiche per anticipare il caso peggiore, ma questa è più un'arte che una scienza.
Aggiungi un po' di zucchero
I compressori vengono usati comunemente nella produzione di musica e giochi per smussare il segnale e controllare gli picchi nel segnale complessivo. Questa funzionalità è disponibile nel mondo dell'audio web tramite DynamicsCompressorNode
, che può essere inserito nel grafico audio per ottenere un suono più intenso, potente e pieno, nonché per facilitare la creazione di clip.
Citando direttamente la specifica, questo nodo
In genere, è consigliabile utilizzare la compressione dinamica, soprattutto in un ambiente di gioco, dove, come discusso in precedenza, non sai esattamente quali suoni verranno riprodotti e quando. Plink di DinahMoe Labs è un ottimo esempio, poiché i suoni riprodotti dipendono completamente da te e dagli altri partecipanti. Un compressore è utile nella maggior parte dei casi, tranne in alcuni rari casi in cui hai a che fare con tracce masterizzate con cura che sono già state sintonizzate per avere un suono "giusto".
Per implementare questa funzionalità, è sufficiente includere un NodeDynamicsCompressor nel grafico audio, in genere come ultimo nodo prima della destinazione.
// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);
Per ulteriori dettagli sulla compressione delle dinamiche, questo articolo di Wikipedia è molto informativo.
Per riepilogare, ascolta attentamente per rilevare eventuali clipping e previeni il problema inserendo un nodo di guadagno principale. Quindi, stringi l'intera traccia utilizzando un nodo compressore dinamico. Il grafico audio potrebbe avere il seguente aspetto:
Conclusione
Ho trattato quelli che ritengo essere gli aspetti più importanti dello sviluppo audio dei giochi con l'API Web Audio. Con queste tecniche, puoi creare esperienze audio davvero avvincenti direttamente nel browser. Prima di chiudere, ti lascio con un suggerimento specifico per il browser: assicurati di mettere in pausa l'audio se la scheda passa in background utilizzando l'API Visibility, altrimenti creerai un'esperienza potenzialmente frustrante per l'utente.
Per ulteriori informazioni su Web Audio, consulta l'articolo introduttivo di inizio e, se hai domande, controlla se la risposta è già presente nelle Domande frequenti su Web Audio. Infine, se hai altre domande, chiedile su Stack Overflow utilizzando il tag web-audio.
Prima di salutarci, vorrei lasciarti guidare da alcuni fantastici usi dell'API Web Audio nei giochi reali di oggi:
- Field Runners e un articolo su alcuni dettagli tecnici.
- Angry Birds, che di recente è passato all'API Web Audio. Per ulteriori informazioni, consulta questo articolo.
- Skid Racer, che fa un uso eccezionale dell'audio spazializzato.