Sviluppare l'audio del gioco con l'API Web Audio

Boris Smus
Boris Smus

Introduzione

L'audio è un elemento fondamentale che rende così coinvolgenti le esperienze multimediali. Se hai mai provato a guardare un film con l'audio disattivato, probabilmente lo 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:

  • Impossibile applicare filtri al segnale audio
  • Impossibile accedere ai dati PCM non elaborati
  • Nessun concetto di posizione e direzione di sorgenti e ascoltatori
  • Nessuna temporizzazione granulare.

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.

GarageBand

Poi, utilizzando l'API Web Audio, puoi importare tutti questi sample utilizzando qualcosa come la classe BufferLoader tramite XHR (questo argomento è trattato in modo approfondito 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, crea un'origine per ogni nodo e un nodo di guadagno per ogni origine e collega il grafico.

Dopodiché, puoi riprodurre contemporaneamente tutte queste sorgenti in loop e, poiché hanno tutte la stessa durata, l'API Web Audio garantisce 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 viene applicato il crossfade tra di loro utilizzando curve di potenza uguali (come descritto nell'introduzione).

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ò essere utilizzato con i contenuti in streaming, il che ti consente di riprodurre immediatamente la musica di sottofondo anziché dover attendere il download di tutti i contenuti. 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 sincronizzate con precisione è uno dei punti di forza dell'API Web Audio.

L'esempio seguente crea un colpo di mitragliatrice da più singoli sample di proiettili creando più sorgenti sonore 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. Naturalmente, variano in base al suono in base alla distanza dal bersaglio e alla posizione relativa (ne parleremo più avanti), ma anche questo potrebbe non essere sufficiente. Fortunatamente, l'API Web Audio offre un modo per modificare facilmente l'esempio riportato sopra in due modi:

  1. Con un leggero spostamento nel tempo tra i colpi
  2. 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 posizionato in stereo può aumentare notevolmente l'immersione dell'esperienza. 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 dell'origine (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();
    }
};

Informazioni importanti sulla gestione della spazializzazione da parte di Web Audio:

  • Per impostazione predefinita, l'ascoltatore si trova all'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.
  • Web Audio utilizza le coordinate cartesiane con asse Y verso l'alto (l'opposto della maggior parte dei sistemi di grafica computerizzata). 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.

Modello di posizione

Al contesto dell'API Web Audio è associato un singolo AudioListener che può essere configurato nello spazio tramite posizione e orientamento. Ogni sorgente può essere passata tramite 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 l'entità del guadagno in base alla vicinanza alla sorgente, mentre il modello direzionale può essere configurato specificando un cono interno ed esterno, che determinano l'entità del 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, consulta 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 ulteriori informazioni 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 molta fatica 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 risposte all'impulso da un determinato ambiente, consulta la sezione "Configurazione della registrazione" nella parte Convolvenza 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 creato un gioco, configurato l'audio posizionale e ora hai un gran numero di AudioNode nel grafico, tutti riprodotti contemporaneamente. 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à dell'altoparlante. Come le immagini che superano i confini della tela, anche i suoni possono essere tagliati se la forma d'onda supera la soglia massima, producendo una distorsione distinta. L'onda ha il seguente aspetto:

Clip

Ecco un esempio reale di clipping in azione. La forma d'onda non è buona:

Clip

È 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 clipping si verifica quando il valore del segnale in un canale supera l'intervallo valido, ovvero 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 della misurazione potrebbe eseguire il polling di 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, dovresti modificare i profitti per anticipare il caso peggiore, ma si tratta più di un'arte che di 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 di Web Audio tramite il plug-in DynamicsCompressorNode, che può essere inserito nel grafico audio per ottenere un suono più alto, ricco e pieno e anche per gestire il clipping. 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 dinamica, 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:

Risultato finale

Conclusione

Ho trattato quelli che ritengo essere gli aspetti più importanti dello sviluppo dell'audio dei giochi con l'API Web Audio. Con queste tecniche, puoi creare esperienze audio davvero coinvolgenti 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 concludere, ecco alcuni fantastici utilizzi dell'API Web Media in giochi reali oggi: