توسعه صدای بازی با Web Audio API

معرفی

صدا بخش بزرگی از چیزی است که تجربه های چندرسانه ای را قانع کننده می کند. اگر تا به حال سعی کرده اید فیلمی را با صدای خاموش تماشا کنید، احتمالا متوجه این موضوع شده اید.

بازی ها نیز از این قاعده مستثنی نیستند! بهترین خاطرات من از بازی های ویدیویی مربوط به موسیقی و جلوه های صوتی است. اکنون، در بسیاری از موارد، نزدیک به دو دهه پس از پخش آهنگ‌های مورد علاقه‌ام، هنوز نمی‌توانم آهنگ‌های Zelda کوجی کوندو و موسیقی متن جوی دیابلو مت Uelmen را از ذهنم بیرون کنم. همین جذابیت در مورد جلوه‌های صوتی مانند پاسخ‌های کلیک واحد با قابلیت تشخیص فوری از Warcraft و نمونه‌هایی از کلاسیک‌های نینتندو نیز صدق می‌کند.

صدای بازی چالش های جالبی را ارائه می دهد. برای ایجاد موسیقی بازی متقاعدکننده، طراحان باید خود را با وضعیت بازی غیرقابل پیش بینی بالقوه ای که بازیکن در آن قرار می گیرد تنظیم کنند. در عمل، بخش هایی از بازی می توانند برای مدت زمان نامعلومی ادامه داشته باشند، صداها می توانند با محیط تعامل داشته باشند و به روش های پیچیده ای ترکیب شوند، مانند جلوه های اتاق و موقعیت نسبی صدا. در نهایت، ممکن است تعداد زیادی صدا به طور همزمان پخش شوند، که همه آنها باید با هم صدای خوبی داشته باشند و بدون اعمال جریمه اجرا شوند.

صدای بازی در وب

برای بازی های ساده، استفاده از تگ <audio> ممکن است کافی باشد. با این حال، بسیاری از مرورگرها پیاده سازی ضعیفی را ارائه می دهند که منجر به اشکالات صوتی و تاخیر زیاد می شود. امیدواریم این یک مشکل موقتی باشد، زیرا فروشندگان سخت تلاش می کنند تا پیاده سازی های مربوطه خود را بهبود بخشند. برای نگاهی اجمالی به وضعیت تگ <audio> ، یک مجموعه آزمایشی خوب در areweplayingyet.org وجود دارد.

با نگاهی عمیق تر به مشخصات تگ <audio> ، با این حال، مشخص می شود که کارهای زیادی وجود دارد که به سادگی نمی توان با آن انجام داد، که جای تعجب نیست، زیرا برای پخش رسانه طراحی شده است. برخی از محدودیت ها عبارتند از:

  • عدم امکان اعمال فیلتر بر روی سیگنال صوتی
  • هیچ راهی برای دسترسی به داده های خام PCM وجود ندارد
  • بدون مفهوم جایگاه و جهت منابع و شنوندگان
  • بدون زمان بندی دقیق

در ادامه مقاله، من به برخی از این موضوعات در زمینه صدای بازی که با Web Audio API نوشته شده است، می پردازم. برای معرفی مختصر این API، نگاهی به آموزش شروع کنید.

موسیقی پس زمینه

بازی‌ها اغلب دارای موسیقی پس‌زمینه هستند که روی یک حلقه پخش می‌شوند.

اگر حلقه شما کوتاه و قابل پیش بینی باشد، می تواند بسیار آزاردهنده باشد. اگر بازیکنی در یک منطقه یا سطح گیر کرده باشد و همان نمونه به طور مداوم در پس زمینه پخش شود، ممکن است ارزش آن را داشته باشد که به تدریج از مسیر حذف شود تا از ناامیدی بیشتر جلوگیری شود. استراتژی دیگر این است که ترکیب هایی با شدت های مختلف داشته باشید که بسته به زمینه بازی به تدریج به یکدیگر تبدیل می شوند.

به عنوان مثال، اگر بازیکن شما در منطقه ای با یک نبرد حماسی رئیس باشد، ممکن است چندین ترکیب متفاوت در محدوده احساسی از جوی تا پیش بینی و شدید داشته باشید. نرم افزار سنتز موسیقی اغلب به شما امکان می دهد چندین میکس (با طول یکسان) را بر اساس یک قطعه با انتخاب مجموعه ای از آهنگ ها برای استفاده در صادرات صادر کنید. به این ترتیب شما مقداری سازگاری درونی دارید و از انتقال‌های ناخوشایند هنگام عبور از یک مسیر به مسیر دیگر جلوگیری می‌کنید.

گروه موسیقی دوستانه

سپس، با استفاده از Web Audio API، می‌توانید همه این نمونه‌ها را با استفاده از چیزی شبیه کلاس BufferLoader از طریق XHR وارد کنید (این موضوع در مقاله مقدماتی Web Audio API به طور کامل توضیح داده شده است. بارگیری صداها زمان می‌برد، بنابراین دارایی‌هایی که در بازی استفاده می‌شوند. باید در بارگذاری صفحه، در شروع سطح، یا شاید به صورت تدریجی در حالی که بازیکن در حال بازی است، بارگذاری شود.

در مرحله بعد، یک منبع برای هر گره و یک گره افزایش برای هر منبع ایجاد می کنید و گراف را به هم وصل می کنید.

پس از انجام این کار، می توانید همه این منابع را به طور همزمان در یک حلقه پخش کنید، و از آنجایی که طول همه آنها یکسان است، Web Audio API تضمین می کند که آنها در یک راستا باقی می مانند. با نزدیک‌تر شدن یا دورتر شدن کاراکتر به نبرد نهایی، بازی می‌تواند با استفاده از الگوریتم مقدار افزایش، مقادیر بهره را برای هر یک از گره‌های مربوطه در زنجیره تغییر دهد:

// 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;
}

در رویکرد بالا، دو منبع همزمان بازی می‌کنند و ما با استفاده از منحنی‌های قدرت برابر (همانطور که در مقدمه توضیح داده شد) بین آنها متقاطع می‌زنیم.

امروزه بسیاری از توسعه دهندگان بازی از تگ <audio> برای موسیقی پس‌زمینه خود استفاده می‌کنند، زیرا برای پخش محتوا مناسب است. اکنون می توانید محتوا را از تگ <audio> به یک زمینه صوتی وب بیاورید.

این تکنیک می‌تواند مفید باشد، زیرا تگ <audio> می‌تواند با محتوای جریانی کار کند، که به شما امکان می‌دهد بلافاصله موسیقی پس‌زمینه را به جای اینکه منتظر بمانید تا دانلود شود، پخش کنید. با وارد کردن جریان به Web Audio API، می توانید جریان را دستکاری یا تجزیه و تحلیل کنید. مثال زیر یک فیلتر پایین گذر را برای موسیقی پخش شده از طریق تگ <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);

برای بحث کاملتر در مورد ادغام تگ <audio> با Web Audio API، این مقاله کوتاه را ببینید.

جلوه های صوتی

بازی ها اغلب در پاسخ به ورودی کاربر یا تغییر در وضعیت بازی، جلوه های صوتی را پخش می کنند. با این حال، مانند موسیقی پس‌زمینه، جلوه‌های صوتی می‌توانند خیلی سریع آزاردهنده شوند. برای جلوگیری از این امر، اغلب مفید است که مجموعه ای از صداهای مشابه اما متفاوت برای پخش داشته باشید. همانطور که در سری Warcraft در پاسخ به کلیک کردن بر روی واحدها مشاهده می شود، این می تواند از تغییرات خفیف نمونه های پا تا تغییرات شدید متفاوت باشد.

یکی دیگر از ویژگی‌های کلیدی جلوه‌های صوتی در بازی‌ها این است که می‌توان تعداد زیادی از آن‌ها را به طور همزمان وجود داشت. تصور کنید در وسط یک درگیری با چندین بازیگر در حال شلیک مسلسل هستید. هر مسلسل چندین بار در ثانیه شلیک می کند و باعث می شود ده ها افکت صوتی همزمان پخش شود. پخش همزمان صدا از چندین منبع با زمان دقیق، جایی است که Web Audio API واقعاً می درخشد.

مثال زیر یک گلوله مسلسل را از چندین نمونه گلوله منفرد با ایجاد چندین منبع صوتی که پخش آنها در زمان تکان می خورد، ایجاد می کند.

var time = context.currentTime;
for (var i = 0; i < rounds; i++) {
    var source = this.makeSource(this.buffers[M4A1]);
    source.noteOn(time + i - interval);
}

حالا، اگر همه مسلسل‌های بازی شما دقیقاً اینگونه صدا می‌کردند، بسیار کسل‌کننده خواهد بود. البته آنها بر اساس صدا بر اساس فاصله از هدف و موقعیت نسبی متفاوت هستند (در ادامه در این مورد بیشتر توضیح خواهیم داد)، اما حتی این ممکن است کافی نباشد. خوشبختانه Web Audio API راهی برای تغییر آسان مثال بالا به دو روش ارائه می دهد:

  1. با یک تغییر ظریف در زمان بین شلیک گلوله
  2. با تغییر نرخ پخش هر نمونه (همچنین تغییر زیر و بم) برای شبیه سازی بهتر تصادفی بودن دنیای واقعی.

برای مثال واقعی‌تر از این تکنیک‌ها، نگاهی به نسخه نمایشی Pool Table بیندازید، که از نمونه‌گیری تصادفی استفاده می‌کند و نرخ پخش را برای صدای جالب‌تر برخورد توپ تغییر می‌دهد.

صدای سه بعدی موقعیتی

بازی ها اغلب در دنیایی با برخی ویژگی های هندسی، چه به صورت دو بعدی یا سه بعدی، تنظیم می شوند. اگر اینطور باشد، صدای استریو می‌تواند به میزان زیادی غوطه‌ور بودن تجربه را افزایش دهد. خوشبختانه، Web Audio API با ویژگی‌های صوتی موقعیتی شتاب‌دار سخت‌افزاری داخلی همراه است که استفاده از آنها کاملاً ساده است. به هر حال، باید مطمئن شوید که بلندگوهای استریو (ترجیحا هدفون) دارید تا مثال زیر منطقی باشد.

در مثال بالا، یک شنونده (آیکون شخص) در وسط بوم وجود دارد و ماوس بر موقعیت منبع (آیکون بلندگو) تأثیر می گذارد. موارد فوق یک مثال ساده از استفاده از AudioPannerNode برای دستیابی به این نوع افکت است. ایده اصلی نمونه بالا پاسخ دادن به حرکت ماوس با تنظیم موقعیت منبع صوتی به شرح زیر است:

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();
    }
};

چیزهایی که باید در مورد روش Web Audio برای فضاسازی بدانید:

  • شنونده به طور پیش فرض در مبدا (0، 0، 0) قرار دارد.
  • APIهای موقعیتی Web Audio بدون واحد هستند، بنابراین من یک ضریب برای بهتر کردن صدای نمایشی معرفی کردم.
  • Web Audio از مختصات دکارتی y-is-up استفاده می کند (برعکس اکثر سیستم های گرافیک کامپیوتری). به همین دلیل است که من محور y را در قطعه بالا عوض می کنم

پیشرفته: مخروط صدا

مدل موقعیتی بسیار قدرتمند و کاملاً پیشرفته است که عمدتاً مبتنی بر OpenAL است. برای جزئیات بیشتر، بخش های 3 و 4 از مشخصات مرتبط بالا را ببینید.

مدل موقعیت

یک AudioListener متصل به زمینه Web Audio API وجود دارد که می تواند در فضا از طریق موقعیت و جهت پیکربندی شود. هر منبع را می توان از طریق یک AudioPannerNode، که صدای ورودی را فضایی می کند، عبور داد. گره پانر دارای موقعیت و جهت و همچنین مدل فاصله و جهت است.

مدل فاصله مقدار بهره را بسته به نزدیکی به منبع مشخص می کند، در حالی که مدل جهتی را می توان با تعیین یک مخروط داخلی و خارجی پیکربندی کرد که اگر شنونده در داخل مخروط داخلی باشد، مقدار بهره (معمولا منفی) را تعیین می کند. مخروط داخلی و خارجی یا خارج از مخروط بیرونی.

var panner = context.createPanner();
panner.coneOuterGain = 0.5;
panner.coneOuterAngle = 180;
panner.coneInnerAngle = 0;

اگرچه مثال من به صورت دو بعدی است، اما این مدل به راحتی به بعد سوم تعمیم می یابد. برای مثالی از فضایی شدن صدا به صورت سه بعدی، این نمونه موقعیتی را ببینید. علاوه بر موقعیت، مدل صدای وب وب نیز به صورت اختیاری شامل سرعت برای جابجایی های داپلر می شود. این مثال اثر داپلر را با جزئیات بیشتری نشان می دهد.

برای اطلاعات بیشتر در مورد این موضوع، این آموزش مفصل در مورد [ترکیب صدای موقعیتی و WebGL][webgl] را بخوانید.

جلوه ها و فیلترهای اتاق

در واقع، نحوه درک صدا تا حد زیادی به اتاقی که آن صدا در آن شنیده می شود بستگی دارد. صدای جیر جیر یکسان در زیرزمین، در مقایسه با یک سالن بزرگ باز، بسیار متفاوت خواهد بود. بازی‌های با ارزش تولید بالا می‌خواهند از این جلوه‌ها تقلید کنند، زیرا ایجاد مجموعه‌ای از نمونه‌های جداگانه برای هر محیط بسیار گران است و حتی به دارایی‌های بیشتر و حجم بیشتری از داده‌های بازی منجر می‌شود.

به زبان ساده، اصطلاح صوتی برای تفاوت بین صدای خام و صدای آن در واقعیت، پاسخ ضربه ای است. این پاسخ‌های ضربه‌ای را می‌توان با زحمت ضبط کرد، و در واقع سایت‌هایی وجود دارند که میزبان بسیاری از این فایل‌های پاسخ ضربه‌ای از پیش ضبط‌شده (ذخیره‌شده به صورت صوتی) برای راحتی شما هستند.

برای اطلاعات بیشتر در مورد نحوه ایجاد پاسخ های ضربه ای از یک محیط خاص، بخش "تنظیم ضبط" در بخش Convolution از مشخصات Web Audio API را مطالعه کنید.

مهمتر از همه برای اهداف ما، Web Audio API یک راه آسان برای اعمال این پاسخ های ضربه ای به صداهای ما با استفاده از 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);

همچنین این نسخه ی نمایشی از جلوه های اتاق را در صفحه مشخصات Web Audio API و همچنین این مثال را مشاهده کنید که به شما امکان کنترل ترکیب خشک (خام) و مرطوب (پردازش شده از طریق convolver) از یک استاندارد عالی Jazz را می دهد.

شمارش معکوس نهایی

بنابراین شما یک بازی ساخته اید، صدای موقعیتی خود را پیکربندی کرده اید و اکنون تعداد زیادی AudioNode در نمودار خود دارید که همه به طور همزمان پخش می شوند. عالی است، اما هنوز یک چیز دیگر وجود دارد که باید در نظر بگیرید:

از آنجایی که چندین صدا بدون عادی سازی روی هم قرار می گیرند، ممکن است در موقعیتی قرار بگیرید که از آستانه توانایی بلندگوی خود فراتر رفته اید. مانند تصاویری که از مرزهای بوم خود فراتر می روند، در صورتی که شکل موج از حداکثر آستانه فراتر رود، صداها نیز می توانند قطع شوند و اعوجاج مشخصی ایجاد کنند. شکل موج چیزی شبیه به این است:

بریدن

در اینجا یک نمونه واقعی از کلیپ در عمل آورده شده است. شکل موج بد به نظر می رسد:

بریدن

مهم است که به اعوجاج‌های خشن مانند آنچه در بالا اشاره شد، یا برعکس، میکس‌های بیش از حد آرامی که شنوندگان شما را مجبور به افزایش صدا می‌کنند، گوش دهید. اگر در این شرایط هستید، واقعاً باید آن را اصلاح کنید!

تشخیص برش

از منظر فنی، برش زمانی اتفاق می‌افتد که مقدار سیگنال در هر کانال از محدوده معتبر، یعنی بین 1- و 1 تجاوز کند. هنگامی که این مورد شناسایی شد، ارائه بازخورد بصری مبنی بر این که این اتفاق می‌افتد مفید است. برای انجام مطمئن این کار، یک JavaScriptAudioNode را در نمودار خود قرار دهید. نمودار صوتی به صورت زیر تنظیم می شود:

// 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);

و بریده شدن را می توان در کنترل کننده 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;
    }
    }
}

به طور کلی، مراقب باشید که به دلایل عملکردی از JavaScriptAudioNode بیش از حد استفاده نکنید. در این مورد، یک پیاده‌سازی جایگزین اندازه‌گیری می‌تواند یک RealtimeAnalyserNode را در نمودار صوتی برای getByteFrequencyData ، در زمان رندر، که توسط requestAnimationFrame تعیین می‌شود، نظرسنجی کند. این روش کارآمدتر است، اما بیشتر سیگنال را از دست می دهد (از جمله مکان هایی که به طور بالقوه در آن قطع می شود)، زیرا رندر حداکثر 60 بار در ثانیه اتفاق می افتد، در حالی که سیگنال صوتی بسیار سریعتر تغییر می کند.

از آنجایی که تشخیص کلیپ بسیار مهم است، احتمالاً در آینده شاهد یک گره داخلی MeterNode Web Audio API خواهیم بود.

جلوگیری از بریدن

با تنظیم بهره در AudioGainNode اصلی، می‌توانید ترکیب خود را تا حدی تحت فشار قرار دهید که از بریدن جلوگیری کند. با این حال، در عمل، از آنجایی که صداهایی که در بازی شما پخش می‌شوند ممکن است به عوامل مختلفی بستگی داشته باشند، تصمیم‌گیری در مورد مقدار بهره اصلی که مانع از بریدن برای همه حالت‌ها می‌شود، می‌تواند دشوار باشد. به طور کلی، برای پیش‌بینی بدترین حالت، باید دستاوردها را تغییر دهید، اما این بیشتر یک هنر است تا یک علم.

کمی شکر اضافه کنید

کمپرسورها معمولاً در تولید موسیقی و بازی برای صاف کردن سیگنال و کنترل اسپایک ها در سیگنال کلی استفاده می شوند. این قابلیت در دنیای Web Audio از طریق DynamicsCompressorNode در دسترس است، که می‌تواند در نمودار صوتی شما قرار داده شود تا صدایی بلندتر، غنی‌تر و کامل‌تر بدهد و همچنین به کلیپ کردن کمک کند. به طور مستقیم به نقل از مشخصات، این گره

استفاده از فشرده سازی دینامیک به طور کلی ایده خوبی است، به خصوص در تنظیمات بازی، جایی که، همانطور که قبلاً بحث شد، دقیقاً نمی دانید چه صداهایی و چه زمانی پخش می شوند. Plink از آزمایشگاه های DinahMoe یک مثال عالی برای این است، زیرا صداهایی که پخش می شوند کاملاً به شما و سایر شرکت کنندگان بستگی دارد. کمپرسور در اکثر موارد مفید است، به جز برخی موارد نادر، که در آن شما با آهنگ‌هایی که به سختی تسلط یافته‌اند سروکار دارید که قبلاً برای صدای «درست» تنظیم شده‌اند.

اجرای این امر به سادگی شامل گنجاندن یک DynamicsCompressorNode در نمودار صوتی شما است، به طور کلی به عنوان آخرین گره قبل از مقصد.

// Assume the output is all going through the mix node.
var compressor = context.createDynamicsCompressor();
mix.connect(compressor);
compressor.connect(context.destination);

برای جزئیات بیشتر در مورد فشرده سازی دینامیک، این مقاله ویکی پدیا بسیار آموزنده است.

به طور خلاصه، برای برش دادن به دقت گوش دهید و با قرار دادن یک گره master gain از آن جلوگیری کنید. سپس کل مخلوط را با استفاده از یک گره کمپرسور دینامیک سفت کنید. نمودار صوتی شما ممکن است چیزی شبیه به این باشد:

نتیجه نهایی

نتیجه

که به نظر من مهمترین جنبه های توسعه صدای بازی با استفاده از Web Audio API را پوشش می دهد. با استفاده از این تکنیک ها، می توانید تجربیات صوتی واقعاً قانع کننده ای را درست در مرورگر خود ایجاد کنید. قبل از اینکه از سیستم خارج شوم، اجازه دهید یک نکته مخصوص مرورگر را به شما بگویم: اگر برگه شما با استفاده از API نمایان بودن صفحه به پس‌زمینه رفت، حتماً صدا را موقتاً متوقف کنید، در غیر این صورت یک تجربه بالقوه ناامیدکننده برای کاربر خود ایجاد خواهید کرد.

برای اطلاعات بیشتر درباره Web Audio، به مقاله مقدماتی شروع نگاهی بیندازید، و اگر سؤالی دارید، ببینید آیا قبلاً در سؤالات متداول صوتی وب پاسخ داده شده است یا خیر. در نهایت، اگر سؤال دیگری دارید، با استفاده از تگ web-audio از آنها در Stack Overflow بپرسید.

قبل از اینکه امضا کنم، اجازه دهید برخی از کاربردهای عالی Web Audio API را در بازی‌های واقعی امروز به شما معرفی کنم: