Jak powstała ścieżka dźwiękowa
Wprowadzenie
JAM with Chrome to internetowy projekt muzyczny stworzony przez Google. JAM with Chrome pozwala użytkownikom z całego świata tworzyć zespoły i grać w czasie rzeczywistym w przeglądarce. My, pracownicy firmy DinahMoe, mieliśmy przyjemność uczestniczyć w tym projekcie. Naszym zadaniem było stworzenie muzyki do aplikacji oraz zaprojektowanie i opracowanie jej komponentu muzycznego. Podczas prac nad tym projektem skupiono się na trzech głównych obszarach: „stacji roboczej do tworzenia muzyki”, która obejmuje odtwarzanie MIDI, samplery programowe, efekty dźwiękowe, kierowanie i miksowanie; silniku logicznym do sterowania muzyką w sposób interaktywny w czasie rzeczywistym; oraz komponencie synchronizacji, który zapewnia, że wszyscy gracze w sesji słyszą muzykę dokładnie w tym samym momencie, co jest warunkiem koniecznym do wspólnej gry.
Aby osiągnąć najwyższy poziom autentyczności, dokładności i jakości dźwięku, zdecydowaliśmy się użyć interfejsu Web Audio API. W tym studium przypadku omówimy niektóre z wyzwanych przez nas problemów i sposoby ich rozwiązania. Na stronie HTML5Rocks znajdziesz już kilka świetnych artykułów wprowadzających, które pomogą Ci zacząć pracę z dźwiękiem w WWW. Dlatego od razu przejdziemy do sedna.
Tworzenie niestandardowych efektów dźwiękowych
Interfejs Web Audio API zawiera wiele przydatnych efektów, ale potrzebowaliśmy bardziej rozbudowanych efektów dla naszych instrumentów w JAM w Chrome. Na przykład w Web Audio jest domyślny węzeł opóźnienia, ale istnieje wiele rodzajów opóźnień – opóźnienie stereo, opóźnienie ping-pong, opóźnienie slapback i tak dalej. Na szczęście wszystkie te efekty można tworzyć w Web Audio, korzystając z rodzajów efektów i odrobiny wyobraźni.
Chcieliśmy, aby można było używać natywnych węzłów i własnych efektów niestandardowych w jak najbardziej przejrzysty sposób, dlatego zdecydowaliśmy się utworzyć format opakowania, który umożliwiłby to. Wbudowane węzły w Web Audio używają metody connect do łączenia ze sobą węzłów, więc musieliśmy emulować to zachowanie. Oto podstawowa koncepcja:
var MyCustomNode = function(){
this.input = audioContext.createGain();
var output = audioContext.createGain();
this.connect = function(target){
output.connect(target);
};
};
W tym przypadku jesteśmy bardzo blisko węzłów natywnych. Zobaczmy, jak to działa.
//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);
Jedyną różnicą między węzłem niestandardowym a domyślnym jest to, że musimy połączyć się z właściwością wejściową węzła niestandardowego. Na pewno istnieją sposoby na obejście tego ograniczenia, ale w naszym przypadku było to wystarczające. Ten wzór można dalej rozwijać, aby symulować metody rozłączania natywnych węzłów AudioNodes, a także uwzględniać definiowane przez użytkownika dane wejściowe i wyjściowe podczas łączenia. Aby dowiedzieć się, do czego służą natywne węzły, zapoznaj się z specyfikacją.
Teraz, gdy mieliśmy już podstawowy schemat tworzenia efektów niestandardowych, następnym krokiem było nadanie niestandardowemu węzłowi niestandardowego zachowania. Przyjrzyjmy się węzłowi opóźnienia slapback.
Slapback like you mean it
Opóźnienie slapback, czasami nazywane opóźnieniem slapback, to klasyczny efekt stosowany na wielu instrumentach, od wokalu w stylu lat 50. po gitary surfowe. Efekt ten polega na tym, że dźwięk wejściowy jest odtwarzany z niewielkim opóźnieniem (około 75–250 ms). Daje to wrażenie, że dźwięk jest odrzucany, stąd nazwa. Efekt można uzyskać w ten sposób:
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);
};
};
Jak niektórzy z Was już pewnie wiedzą, ten delay może być używany również z większymi opóźnieniami, co czyni go zwykłym mono delay z feedbackiem. Oto przykład użycia tego opóźnienia, który pozwoli Ci usłyszeć, jak to brzmi.
Kierowanie dźwięku
Podczas pracy z różnymi instrumentami i partiami muzycznymi w profesjonalnych aplikacjach audio bardzo ważne jest posiadanie elastycznego systemu routingu, który umożliwia skuteczne miksowanie i modulowanie dźwięków. W JAM z Chrome opracowaliśmy system magistrali audio podobny do tych, które można znaleźć na fizycznych konsolach miksujących. Dzięki temu możemy podłączyć wszystkie instrumenty, które potrzebują efektu pogłosu, do wspólnego kanału, a następnie dodać pogłos do tego kanału zamiast dodawać go do każdego osobnego instrumentu. Jest to ważna optymalizacja i zalecamy, aby stosować podobne rozwiązania, gdy tylko zaczniesz tworzyć bardziej złożone aplikacje.
Na szczęście w Web Audio jest to bardzo proste. Możemy użyć szkieletu zdefiniowanego dla efektów i stosować go w taki sam sposób.
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);
};
};
Można go użyć w ten sposób:
//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);
I voilà, zastosowaliśmy opóźnienie, korekcję i pogłos (który jest dość drogim efektem pod względem wydajności) za połowę ceny, niż gdybyśmy zastosowali efekty do każdego osobnego instrumentu. Jeśli chcemy dodać nieco pikanterii, możemy dodać 2 nowe węzły wzmocnienia – preGain i postGain, które pozwolą nam wyłączyć lub stonować dźwięki w autobusie na 2 różne sposoby. Wartość preGain jest umieszczana przed efektami, a postGain na końcu łańcucha. Jeśli potem zniżymy poziom preGain, efekty będą nadal rezonować po osiągnięciu przez gain najniższego poziomu, ale jeśli zniżymy poziom postGain, cały dźwięk zostanie wyciszony w tym samym momencie.
Dokąd teraz?
Opisane przeze mnie metody można i należy rozwijać. Elementy takie jak dane wejściowe i wyjściowe węzłów niestandardowych oraz metody łączenia mogą lub powinny być implementowane za pomocą dziedziczenia na podstawie prototypu. Autobusy powinny mieć możliwość dynamicznego tworzenia efektów po przekazaniu im listy efektów.
Aby uczcić wydanie JAM w Chrome, postanowiliśmy udostępnić nasz framework efektów jako oprogramowanie open source. Jeśli ten krótki wstęp wzbudził Twoje zainteresowanie, zapoznaj się z tym tematem i wesprzyj społeczność. Tutaj trwa dyskusja na temat ujednolicania formatu niestandardowych elementów dźwięku w internecie. Zaangażuj się