Örnek Olay - The Sounds of Racer

Giriş

Racer çok oyunculu ve çok cihazlı bir Chrome Denemesidir. Farklı ekranlarda oynanan retro tarzı bir slot araba oyunu. Telefon veya tabletlerde, Android ya da iOS'te. Herkes katılabilir. Uygulama yok İndirmeye gerek yok. Sadece mobil web.

Plan8, 14islands'deki arkadaşlarımızla birlikte Giorgio Moroder'in orijinal bestesine dayanarak dinamik bir müzik ve ses deneyimi oluşturdu. Yarışçıda duyarlı motor sesleri, yarış ses efektleri ve daha da önemlisi, yarışçılar katıldıkça kendini birden fazla cihazda dağıtan dinamik bir müzik mix'i bulunuyor. Bu, akıllı telefonlardan oluşan çok hoparlörlü bir kurulumdur.

Birden fazla cihazı birbirine bağlamak uzun süredir öyle. Sesin farklı cihazlarda bölündüğü veya cihazlar arasında atladığı müzik deneyleri yapmıştık. Bu nedenle bu fikirleri Racer oyununa uygulamak istiyorduk.

Daha net bir ifadeyle, oyuna katılanların sayısı giderek arttığından müzik parçasını cihazlarda oluşturup oluşturamayacağımızı test etmek istedik. Bu testler başta davul ve bas ile başlayıp gitar ve synth gibi parçalarla başladı. Müzik demoları yaptık ve kodlamaya başladık. Çoklu hoparlör efekti gerçekten tatmin ediciydi. Şu an için senkronizasyonu tam anlamıyla kuramıyorduk, ancak ses katmanlarının cihazlara yayıldığını duyduğumuzda iyi bir şey yaptığımızı biliyorduk.

Sesler oluşturuluyor

Google Creative Lab, ses ve müziğe yaratıcı bir yön verdi. Gerçek sesleri kaydetmek veya ses kitaplıklarına başvurmak yerine ses efekti oluşturmak için analog sentezleyiciler kullanmak istiyorduk. Ayrıca çıkış hoparlörünün çoğu durumda küçük bir telefon veya tablet hoparlörü olacağını biliyorduk, bu nedenle hoparlörlerin bozulmaması için seslerin frekans spektrumunun sınırlı olması gerekti. Bu süreçte oldukça zorlandık. Giorgio'dan ilk müzik taslaklarını aldığımızda rahatlamıştık çünkü Giorgio'nun bestesi bizim oluşturduğumuz seslerle mükemmel şekilde çalışıyordu.

Motor sesi

Sesleri programlamadaki en büyük zorluk, en iyi motor sesini bulmak ve davranışını şekillendirmekti. Yarış pisti F1 veya Nascar pistine benziyordu. Bu nedenle arabaların hızlı ve patlayıcı bir pist hissetmesi gerekiyordu. Aynı zamanda arabalar çok küçük olduğundan büyük bir motor sesi, sesi görsellerle pek bağdaştıramıyordu. Mobil hoparlörde güçlü bir gürültülü motorumuz zaten çalışmıyordu ve bu yüzden başka bir şey bulmamız gerekiyordu.

İlham almak için arkadaşımız Jon Ekstrand'ın modüler synth'lerinden oluşan koleksiyonumuza bağladık ve çeşitli düzenlemeler yapmaya başladık. Duyduklarımızı beğendik. İki osilatör, güzel filtreler ve LFO'nun sesi böyle oldu.

Analog ekipman, Web Audio API kullanılarak büyük bir başarıyla yenilenmiş olduğundan Web Audio'da basit bir synth'ler oluşturmaya başladık. Üretilen ses en duyarlı sestir, ancak cihazın işlem gücünden vergilendirilir. Görsellerin sorunsuz çalışması için tüm kaynakları muhafaza etmek üzere son derece istekli olmamız gerekiyordu. Bu nedenle, tekniği değiştirdik ve onun yerine ses örneklerini oynattık.

Motor sesi ilhamı için modüler synth

Örneklerden motor sesi çıkarmak için kullanılabilecek birkaç teknik vardır. Konsol oyunlarında en yaygın yaklaşım, farklı BGBG'lerde (yükle) motorun birden çok ses katmanına sahip olmak (ne kadar iyi o kadar iyidir) ve daha sonra, aralarında geçiş ve geçiş yapmaktır. Sonra, aynı BGBG'de sadece devir (yüksüz) yapan ve aralarında geçiş ve çapraz geçiş bulunan birden fazla motorun sesini içeren bir katman ekleyin. Vites değiştirirken bu katmanlar arasında geçiş yapmak, doğru yapılırsa kulağa çok gerçekçi gelir, ancak bunu yalnızca çok fazla ses dosyanız varsa yapabilirsiniz. Çapraz geçiş çok geniş olamaz veya kulağa çok sentetik gelir. Uzun yükleme sürelerinden kaçınmamız gerektiğinden bu seçenek bize uygun değildi. Her katman için beş veya altı ses dosyası kullanmayı denedik, ancak ses moral bozucuydu. Daha az dosya gerektiren bir yol bulmamız gerekiyordu.

En etkili çözüm şuydu:

  • En yüksek perde / devirde programlanmış bir döngüyle biten arabanın görsel ivmesiyle senkronize edilmiş hızlanma ve vites değiştirme içeren bir ses dosyası. Web Audio API'sı hassas döngü oluşturma konusunda çok iyi, dolayısıyla bunu kesintiler veya patlamalar olmadan yapabiliyoruz.
  • Yavaşlama / motor yavaşlayan tek ses dosyası.
  • Son olarak da sabit / boşta sesi döngü halinde çalan bir ses dosyası var.

Benzer

Motor ses grafiği

İlk dokunma etkinliği / hızlandırma için ilk dosyayı baştan oynatırdık ve oynatıcı kısıtlamayı serbest bırakırsa ses dosyasında bulunduğumuz süreyi hesaplıyorduk. Böylece, kısıtlama tekrar açıldığında, ikinci (geri gelir) dosya çalındıktan sonra hızlandırma dosyasında doğru yere atlıyordu.

function throttleOn(throttle) {
    //Calculate the start position depending 
    //on the current amount of throttle.
    //By multiplying throttle we get a start position 
    //between 0 and 3 seconds.
    var startPosition = throttle * 3;

    var audio = context.createBufferSource();
    audio.buffer = loadedBuffers["accelerate_and_loop"];

    //Sets the loop positions for the buffer source.
    audio.loopStart = 5;
    audio.loopEnd = 9;

    //Starts the buffer source at the current time
    //with the calculated offset.
    audio.start(context.currentTime, startPosition);
}

Deneyin.

Motoru çalıştırın ve "Gaz pedalı" düğmesine basın.

<input type="button" id="playstop" value = "Start/Stop Engine" onclick='playStop()'>
<input type="button" id="throttle" value = "Throttle" onmousedown='throttleOn()' onmouseup='throttleOff()'>

Yani sadece üç küçük ses dosyası ve iyi ses motoru olduğundan bir sonraki görevimize geçmeye karar verdik.

Senkronizasyonu alma

14 adalarından David Lindkvist ile birlikte cihazların mükemmel uyum içinde çalışmasını sağlamak için daha derinlemesine araştırmaya başladık. Temel teori oldukça basit. Cihaz, sunucuya zamanını sorar, ağdaki gecikmeyi hesaba katar, ardından yerel saat farkını hesaplar.

syncOffset = localTime - serverTime - networkLatency

Bu ofset sayesinde her bağlı cihaz aynı zaman kavramını paylaşır. Kolay, değil mi? (Teoride tekrarlıyorum.)

Ağ gecikmesi hesaplanıyor

Gecikmenin, sunucudan yanıt istemek ve yanıt almak için gereken sürenin yarısı kadar olduğunu varsayabiliriz:

networkLatency = (receivedTime - sentTime) × 0.5

Bu varsayımdaki sorun, sunucuya yapılan gidiş dönüşün her zaman simetrik olmamasıdır (yani isteğin yanıttan daha uzun sürmesi veya isteğin tersi olabilir). Ağ gecikmesi ne kadar yüksek olursa bu asimetri o kadar büyük etki yaratır. Bu da seslerin gecikmesine ve diğer cihazlarla senkronize bir şekilde çalınmasına neden olur.

Neyse ki beynimiz, seslerin biraz gecikmeli olduğunu fark etmeyecek şekilde programlanmış. Araştırmalar, beynimizin sesleri ayrı olarak algılamasının 20-30 milisaniye (ms)lik bir gecikmeyle geçtiğini göstermiştir. Bununla birlikte, yaklaşık 12 ila 15 ms.de, tam olarak "algılamasanız bile" geciken bir sinyalin etkilerini "hissetmeye" başlarsınız. Yerleşik birkaç zaman senkronizasyonu protokollerini ve daha basit alternatifleri araştırdık ve bazılarını pratikte uygulamaya çalıştık. Sonunda, Google'ın düşük gecikmeli altyapısı sayesinde çok sayıda istek örneğini kolayca bulabildik ve en düşük gecikmeli örneği referans olarak kullanabildik.

Saat kaymasıyla mücadele

İşe yaradı! 5'ten fazla cihazımız vardı ve bunlar mükemmel bir senkronizasyonla nabzını tutuyor. Ancak bir süredir yalnızca bir süredir. Son derece hassas Web Audio API bağlam zamanını kullanarak sesi planlamış olmamıza rağmen birkaç dakika oynadıktan sonra cihazlar birbirlerinden ayrılıyordu. Gecikme yavaş bir şekilde, bir kerede yalnızca birkaç milisaniye uzanıyor ve ilk başta algılanamıyordu. Ancak uzun süre oynadıktan sonra müzik katmanlarının tamamen senkronize olmamasına neden oluyordu. Merhaba, kayma.

Çözüm, birkaç saniyede bir yeniden senkronize etmek, yeni bir saat ofseti hesaplamak ve bunu kolayca ses planlayıcıya aktarmaktı. Ağ gecikmesi nedeniyle müzikte kayda değer değişiklikler olması riskini azaltmak için en son senkronizasyon ofsetlerinin geçmişini tutarak ve ortalama hesaplayarak değişikliği yumuşatmaya karar verdik.

Şarkı planlama ve düzenlemeleri değiştirme

Etkileşimli bir ses deneyimi oluşturmak, mevcut durumu değiştirmek için kullanıcının eylemlerine bağımlı olduğunuzdan, artık şarkının bazı bölümlerinin ne zaman çalacağını kontrol edemeyeceğiniz anlamına gelir. Şarkıdaki aranjmanlar arasında zamanında geçiş yapabilmemiz gerekiyordu. Bu da planlayıcımızın bir sonraki düzenlemeye geçmeden önce şu anda çalan çubuğun ne kadar kaldığını hesaplayabilmesi gerektiği anlamına geliyordu. Algoritmamız şu şekilde sonuçlandı:

  • Client(1) şarkıyı başlatır.
  • Client(n), birinci müşteriye şarkının ne zaman çalındığını sorar.
  • Client(n), SyncOffset'i ve ses bağlamının oluşturulmasından itibaren geçen süreyi hesaba katarak şarkının Web Audio bağlamını kullanarak başladığı zamana ilişkin bir referans noktasını hesaplar.
  • playDelta = Date.now() - syncOffset - songStartTime - context.currentTime
  • Client(n), playDelta'yı kullanarak şarkının ne kadar süreyle çalışmakta olduğunu hesaplar. Şarkı planlayıcı, mevcut düzenlemede hangi çubuğun bir sonra çalınması gerektiğini belirlemek için bu bilgiyi kullanır.
  • playTime = playDelta + context.currentTime nextBar = Math.ceil((playTime % loopDuration) ÷ barDuration) % numberOfBars

Daha sağlıklı olmak adına düzenlemelerimizi her zaman sekiz bar uzunluğunda ve aynı tempoda (dakika başına vuruş) olacak şekilde sınırlandırdık.

Önünüze bakın

JavaScript'te setTimeout veya setInterval kullanırken planlama yapmak her zaman önemlidir. Bunun nedeni, JavaScript saatinin çok hassas olmaması ve planlı geri çağırmaların düzen, oluşturma, atık toplama ve XMLHTTPRequests nedeniyle on milisaniye veya daha uzun bir süre içinde kolayca saptırılabilmesidir. Bizim örneğimizde tüm müşterilerin aynı etkinliği ağ üzerinden alması için gereken süreyi de hesaba katmamız gerekti.

Ses sprite görseller

Sesleri tek bir dosyada birleştirmek, hem HTML Audio hem de Web Audio API'sı için HTTP isteklerini azaltmanın etkili bir yoludur. Ayrıca, ses nesnesini kullanarak sesleri duyarlı bir şekilde çalmanın en iyi yolu da budur, çünkü çalmadan önce yeni bir ses nesnesi yüklemek zorunda değildir. Başlangıç noktası olarak kullandığımız bazı iyi uygulamalar halihazırda mevcut. Hem iOS hem de Android'de güvenilir bir şekilde çalışacak ve cihazların uykuya dalmasıyla ilgili bazı garip durumları ele alacak şekilde gücümüzü genişlettik.

Android'de, cihazı uyku moduna geçirseniz bile ses öğeleri çalmaya devam eder. Uyku modunda, JavaScript yürütme işlemi pil tasarrufu için sınırlıdır ve geri çağırmaları tetiklemek için requestAnimationFrame, setInterval veya setTimeout hizmetine güvenemezsiniz. Ses sprite görselleri, oynatmanın durdurulup durdurulmayacağını kontrol etmeye devam etmek için JavaScript'e dayandığından bu bir sorundur. Daha da kötüsü, bazı durumlarda ses hâlâ çalındığı için ses öğesinin currentTime öğesi güncellenmiyor.

Chrome Racer'da Web harici bir ses yedeği olarak kullandığımız AudioSprite uygulamasına göz atın.

Ses öğesi

Racer üzerinde çalışmaya başladığımızda, Android için Chrome henüz Web Audio API'sını desteklemiyordu. Bazı cihazlar için HTML Audio kullanmanın mantığı, diğerleri içinse Web Audio API'sı ile ulaşmak istediğimiz gelişmiş ses çıkışının birleştirilmesi bazı ilginç zorlukların önünü açtı. Neyse ki artık hepsi tarih oldu. Web Audio API, Android M28 beta sürümünde uygulanmıştır.

  • Gecikmeler/zamanlama sorunları. Ses öğesi her zaman tam olarak çalmasını belirttiğiniz anda oynatılmaz. JavaScript tek iş parçacığı biçiminde olduğundan tarayıcı meşgul olabilir, bu da iki saniyeye kadar oynatma gecikmelerine neden olur.
  • Oynatma gecikmeleri, kesintisiz döngünün her zaman mümkün olmadığı anlamına gelir. Masaüstünde boşluksuz döngüler elde etmek için çift arabelleğe alma özelliğini kullanabilirsiniz ancak mobil cihazlarda bu bir seçenek değildir, çünkü:
    • Çoğu mobil cihaz aynı anda birden fazla Ses öğesi çalmaz.
    • Sabit ses düzeyi. Android de iOS da bir Ses nesnesinin ses düzeyini değiştirmenize izin vermez.
  • Önceden yükleme yok. Mobil cihazlarda, Ses öğesi bir touchStart işleyicide çalma başlatılmadığı sürece kaynağını yüklemeye başlamaz.
  • Sorunlar aranıyor. Sunucunuz HTTP Bayt Aralığı özelliğini desteklemediği sürece duration değeri alma veya currentTime ayarlama işlemi başarısız olur. Bizim gibi bir ses görüntüsü oluşturuyorsanız buna dikkat edin.
  • MP3'te Temel Kimlik Doğrulama başarısız oluyor. Kullandığınız tarayıcı ne olursa olsun bazı cihazlar Temel Kimlik Doğrulama ile korunan MP3 dosyalarını yükleyemez.

Sonuçlar

Web'de sesle başa çıkmada en iyi seçenek olan sesi kapatma düğmesine basmadan bu yana uzun bir yol katettik. Ancak bu daha başlangıç ve web sesi, son derece heyecanlı olmaya başladı. Birden çok cihazın senkronize edilmesi söz konusu olduğunda neler yapılabileceğini ele almıştık. Sinyal işlemeyi ve efektleri (yankı gibi) ayrıntılı bir şekilde incelemek için telefonların ve tabletlerin işlem gücüne sahip değildik ancak cihaz performansı arttıkça web tabanlı oyunlar da bu özelliklerden yararlanmaya başlayacak. Bunlar, sesin olanaklarını geliştirmeye devam etmenin heyecan verici zamanları.