Come abbiamo creato un audio fantastico
Introduzione
JAM with Chrome è un progetto musicale basato sul web creato da Google. JAM con Chrome consente a persone di tutto il mondo di formare una band e suonare in tempo reale all'interno del browser. Noi di DinahMoe abbiamo avuto il grande piacere di partecipare a questo progetto. Il nostro ruolo era produrre musica per l'applicazione, nonché progettare e sviluppare il componente musicale. Lo sviluppo è consistito in tre aree principali: una "music workstation" che include la riproduzione MIDI, i sampler software, gli effetti audio, il routing e il missaggio; un motore di logica musicale per controllare la musica in modo interattivo in tempo reale; e un componente di sincronizzazione che assicura che tutti i giocatori di una sessione ascoltino la musica esattamente nello stesso momento, un prerequisito per poter suonare insieme.
Per ottenere il massimo livello possibile di autenticità, accuratezza e qualità audio, abbiamo scelto di utilizzare l'API Web Audio. Questo case study illustra alcune delle sfide che abbiamo dovuto affrontare e come le abbiamo risolte. Su HTML5Rocks sono già disponibili diversi articoli introduttivi utili per iniziare a utilizzare Web Audio, quindi passeremo subito alla parte più complessa.
Scrivere effetti audio personalizzati
L'API Web Audio include una serie di effetti utili nella specifica, ma avevamo bisogno di effetti più elaborati per i nostri strumenti in JAM con Chrome. Ad esempio, in Web Audio è presente un nodo di ritardo nativo, ma esistono molti tipi di ritardi: ritardo stereo, ritardo ping pong, ritardo slapback e l'elenco continua. Fortunatamente, è possibile creare tutti questi effetti in Web Audio utilizzando i nodi di effetti nativi e un po' di fantasia.
Poiché volevamo poter utilizzare i nodi nativi e i nostri effetti personalizzati nel modo più trasparente possibile, abbiamo deciso di creare un formato wrapper che potesse farlo. I nodi nativi in Web Audio utilizzano il metodo connect per collegare i nodi tra loro, quindi abbiamo dovuto emulare questo comportamento. Ecco l'idea di base:
var MyCustomNode = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
this.connect = function(target){
output.connect(target);
};
};
Con questo pattern siamo molto vicini ai nodi nativi. Vediamo come viene utilizzato.
//create a couple of native nodes and our custom node
var gain = audioContext.createGain(),
customNode = new MyCustomNode(),
anotherGain = audioContext.createGain();
//connect our custom node to the native nodes and send to the output
gain.connect(customNode.input);
customNode.connect(anotherGain);
anotherGain.connect(audioContext.destination);
L'unica differenza tra il nostro nodo personalizzato e uno nativo è che dobbiamo collegarci alla proprietà di input dei nodi personalizzati. Sicuramente ci sono dei modi per aggirare il problema, ma questo era abbastanza simile per i nostri scopi. Questo pattern può essere ulteriormente sviluppato per simulare i metodi di disconnessione degli AudioNode nativi, nonché per supportare input/output definiti dall'utente durante la connessione e così via. Dai un'occhiata alla specifica per scoprire cosa possono fare i nodi nativi.
Ora che avevamo il pattern di base per creare effetti personalizzati, il passaggio successivo era assegnare al nodo personalizzato un comportamento personalizzato. Diamo un'occhiata a un nodo di ritardo slapback.
Slapback come se non ci fosse un domani
Il ritardo slapback, a volte chiamato eco slapback, è un effetto classico utilizzato su una serie di strumenti, dalle parti vocali in stile anni '50 alle chitarre surf. L'effetto prende l'audio in entrata e riproduce una copia con un leggero ritardo di circa 75-250 millisecondi. Questo dà la sensazione che il suono venga schiaffeggiato, da qui il nome. Possiamo creare l'effetto nel seguente modo:
var SlapbackDelayNode = function(){
//create the nodes we'll use
this.input = audioContext.createGain();
var output = audioContext.createGain(),
delay = audioContext.createDelay(),
feedback = audioContext.createGain(),
wetLevel = audioContext.createGain();
//set some decent values
delay.delayTime.value = 0.15; //150 ms delay
feedback.gain.value = 0.25;
wetLevel.gain.value = 0.25;
//set up the routing
this.input.connect(delay);
this.input.connect(output);
delay.connect(feedback);
delay.connect(wetLevel);
feedback.connect(delay);
wetLevel.connect(output);
this.connect = function(target){
output.connect(target);
};
};
Come alcuni di voi potrebbero aver già capito, questo ritardo può essere utilizzato anche con tempi di ritardo più lunghi e diventare così un normale ritardo mono con feedback. Ecco un esempio di utilizzo di questo ritardo per farti sentire come suona.
Routing dell'audio
Quando si lavora con diversi strumenti e parti musicali in applicazioni audio professionali, è essenziale disporre di un sistema di routing flessibile che consenta di mixare e modulare i suoni in modo efficace. In JAM con Chrome abbiamo sviluppato un sistema di bus audio, simile a quelli presenti nelle console di missaggio fisiche. In questo modo possiamo collegare tutti gli strumenti che richiedono un effetto di riverbero a un bus o canale comune e aggiungere il riverbero a quel bus anziché a ogni strumento separato. Si tratta di un'ottimizzazione importante ed è consigliabile fare qualcosa di simile non appena inizi a creare applicazioni più complesse.
Fortunatamente, è molto facile da ottenere in Web Audio. Possiamo utilizzare lo scheletro che abbiamo definito per gli effetti e utilizzarlo nello stesso modo.
var AudioBus = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
//create effect nodes (Convolver and Equalizer are other custom effects from the library presented at the end of the article)
var delay = new SlapbackDelayNode(),
convolver = new tuna.Convolver(),
equalizer = new tuna.Equalizer();
//route 'em
//equalizer -> delay -> convolver
this.input.connect(equalizer);
equalizer.connect(delay.input);
delay.connect(convolver);
convolver.connect(output);
this.connect = function(target){
output.connect(target);
};
};
Questo viene utilizzato nel seguente modo:
//create some native oscillators and our custom audio bus
var bus = new AudioBus(),
instrument1 = audioContext.createOscillator(),
instrument2 = audioContext.createOscillator(),
instrument3 = audioContext.createOscillator();
//connect our instruments to the same bus
instrument1.connect(bus.input);
instrument2.connect(bus.input);
instrument3.connect(bus.input);
bus.connect(audioContext.destination);
Ecco fatto, abbiamo applicato delay, equalizzazione e riverbero (un effetto piuttosto costoso in termini di prestazioni) a metà del costo rispetto all'applicazione degli effetti a ogni strumento separato. Se volessimo aggiungere un po' di pepe al bus, potremmo aggiungere due nuovi nodi di guadagno, preGain e postGain, che ci consentirebbero di disattivare o attenuare i suoni in un bus in due modi diversi. preGain viene inserito prima degli effetti e postGain alla fine della catena. Se riduciamo il preGain, gli effetti continueranno a risuonare dopo che il guadagno avrà raggiunto il minimo, ma se riduciamo il postGain, tutto l'audio verrà disattivato contemporaneamente.
Che cosa vuoi fare adesso?
Questi metodi che ho descritto qui possono e devono essere ulteriormente sviluppati. Elementi come l'input e l'output dei nodi personalizzati e i metodi di connessione potrebbero/dovrebbero essere implementati utilizzando l'eredità basata su prototipi. I bus devono essere in grado di creare effetti dinamicamente ricevendo un elenco di effetti.
Per celebrare il lancio di JAM con Chrome, abbiamo deciso di rendere il nostro framework di effetti open source. Se questa breve introduzione ti ha incuriosito, dai un'occhiata e non esitare a dare il tuo contributo. Qui è in corso una discussione sulla standardizzazione di un formato per le entità Web Audio personalizzate. Partecipa