شروع به کار کردن با Web Audio API

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

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

شروع به کار با AudioContext

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

یک نمونه از AudioContext می‌تواند از چندین ورودی صدا و نمودارهای صوتی پیچیده پشتیبانی کند، بنابراین برای هر برنامه صوتی که ایجاد می‌کنیم، فقط به یکی از این‌ها نیاز خواهیم داشت.

قطعه کد زیر یک AudioContext ایجاد می‌کند:

var context;
window.addEventListener('load', init, false);
function init() {
    try {
    context = new AudioContext();
    }
    catch(e) {
    alert('Web Audio API is not supported in this browser');
    }
}

برای مرورگرهای قدیمی‌تر مبتنی بر WebKit، از پیشوند webkit استفاده کنید، مانند webkitAudioContext.

بسیاری از عملکردهای جالب Web Audio API مانند ایجاد AudioNodes و رمزگشایی داده‌های فایل صوتی از روش‌های AudioContext هستند.

درحال بار کردن صداها

Web Audio API از AudioBuffer برای صداهای کوتاه تا متوسط استفاده می‌کند. رویکرد اصلی استفاده از XMLHttpRequest برای واکشی فایل‌های صوتی است.

این میانای برنامه‌سازی کاربردی از بار کردن داده‌های فایل صوتی در قالب‌های متعدد، مثل WAV،‏ MP3،‏ AAC،‏ OGG، و غیره پشتیبانی می‌کند. پشتیبانی مرورگر از قالب‌های صوتی مختلف متفاوت است.

قطعه کد زیر بار کردن نمونه صدا را نشان می‌دهد:

var dogBarkingBuffer = null;
var context = new AudioContext();

function loadDogSound(url) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.responseType = 'arraybuffer';

    // Decode asynchronously
    request.onload = function() {
    context.decodeAudioData(request.response, function(buffer) {
        dogBarkingBuffer = buffer;
    }, onError);
    }
    request.send();
}

داده‌های فایل صوتی دودویی (نه نوشتاری) است، بنابراین responseType درخواست را روی 'arraybuffer' تنظیم می‌کنیم. برای اطلاعات بیشتر درباره ArrayBuffers، این مقاله درباره XHR2 را ببینید.

Once the (undecoded) audio file data has been received, it can be kept around for later decoding, or it can be decoded right away using the AudioContext decodeAudioData() method. این روش ArrayBuffer داده‌های فایل صوتی ذخیره‌شده در request.response را می‌گیرد و آن را به‌صورت ناهمگام رمزگشایی می‌کند (رشته اجرای اصلی جاوا اسکریپت را مسدود نمی‌کند).

وقتی decodeAudioData() تمام می‌شود، تابع بازخوانی را فراخوانی می‌کند که داده‌های صوتی PCM رمزگشایی‌شده را به‌عنوان AudioBuffer ارائه می‌دهد.

درحال پخش صداها

وقتی یک یا چند AudioBuffers بار شد، آماده پخش صدا هستیم. فرض کنیم که به‌تازگی یک AudioBuffer را با صدای پارس سگ بار کرده‌ایم و بار کردن تمام شده است. سپس می‌توانیم این میان‌گیر را با کد زیر پخش کنیم.

var context = new AudioContext();

function playSound(buffer) {
    var source = context.createBufferSource(); // creates a sound source
    source.buffer = buffer;                    // tell the source which sound to play
    source.connect(context.destination);       // connect the source to the context's destination (the speakers)
    source.noteOn(0);                          // play the source now
}

این تابع playSound() می‌تواند هربار که کسی کلیدی را فشار می‌دهد یا با موشواره روی چیزی کلیک می‌کند فراخوانی شود.

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

خلاصه کردن Web Audio API

البته بهتر است سیستم بارگیری عمومی‌تری ایجاد کنیم که برای بارگیری این صدای خاص کدنویسی نشده باشد. رویکردهای زیادی برای سروکار داشتن با صداهای کوتاه تا متوسطی که یک برنامه صوتی یا بازی استفاده می‌کند وجود دارد - در اینجا یک روش بااستفاده از BufferLoader (بخشی از استاندارد وب نیست) ارائه شده است.

در زیر مثالی از نحوه استفاده از کلاس BufferLoader آورده شده است. بیایید دو AudioBuffers ایجاد کنیم؛ و به‌محض اینکه بار شدند، بیایید آن‌ها را هم‌زمان بازپخش کنیم.

window.onload = init;
var context;
var bufferLoader;

function init() {
    context = new AudioContext();

    bufferLoader = new BufferLoader(
    context,
    [
        '../sounds/hyper-reality/br-jam-loop.wav',
        '../sounds/hyper-reality/laughter.wav',
    ],
    finishedLoading
    );

    bufferLoader.load();
}

function finishedLoading(bufferList) {
    // Create two sources and play them both together.
    var source1 = context.createBufferSource();
    var source2 = context.createBufferSource();
    source1.buffer = bufferList[0];
    source2.buffer = bufferList[1];

    source1.connect(context.destination);
    source2.connect(context.destination);
    source1.noteOn(0);
    source2.noteOn(0);
}

مدیریت زمان: پخش صداها با ریتم

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

الگوی ساده درام راک

که در آن های‌هت در هر نت هشتم نواخته می‌شود و کیک و اسنیر به‌طور متناوب در هر نت چهارم، در کسر میزان ۴/۴ نواخته می‌شوند.

Supposing we have loaded the kick, snare and hihat buffers, the code to do this is simple:

for (var bar = 0; bar < 2; bar++) {
    var time = startTime + bar * 8 * eighthNoteTime;
    // Play the bass (kick) drum on beats 1, 5
    playSound(kick, time);
    playSound(kick, time + 4 * eighthNoteTime);

    // Play the snare drum on beats 3, 7
    playSound(snare, time + 2 * eighthNoteTime);
    playSound(snare, time + 6 * eighthNoteTime);

    // Play the hi-hat every eighth note.
    for (var i = 0; i < 8; ++i) {
    playSound(hihat, time + i * eighthNoteTime);
    }
}

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

function playSound(buffer, time) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    source.connect(context.destination);
    source.noteOn(time);
}

تغییر دادن میزان صدای یک صدا

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

روندنمای ساده‌ای که جریان صدا را از «منبع» به GainNode و سپس به «مقصد» نشان می‌دهد.

این اتصال را می‌توان به‌صورت زیر راه‌اندازی کرد:

// Create a gain node.
var gainNode = context.createGainNode();
// Connect the source to the gain node.
source.connect(gainNode);
// Connect the gain node to the destination.
gainNode.connect(context.destination);

پس‌از راه‌اندازی نمودار، می‌توانید با دستکاری gainNode.gain.value به‌صورت زیر، میزان صدا را به‌صورت برنامه‌نویسی تغییر دهید:

// Reduce the volume.
gainNode.gain.value = 0.5;

محو شدن تدریجی بین دو صدا

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

این کار را می‌توان با نمودار صوتی زیر انجام داد:

نمودار صوتی با دو منبع متصل به گره‌های بهره جداگانه که سپس به یک مقصد هدایت می‌شوند.

برای راه‌اندازی این، ما به‌سادگی دو AudioGainNodes ایجاد می‌کنیم و هر منبع را ازطریق گره‌ها بااستفاده از چیزی شبیه این تابع متصل می‌کنیم:

function createSource(buffer) {
    var source = context.createBufferSource();
    // Create a gain node.
    var gainNode = context.createGainNode();
    source.buffer = buffer;
    // Turn on looping.
    source.loop = true;
    // Connect source to gain.
    source.connect(gainNode);
    // Connect gain to destination.
    gainNode.connect(context.destination);

    return {
    source: source,
    gainNode: gainNode
    };
}

محو تدریجی متقاطع با قدرت برابر

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

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

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

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

محو تدریجی متقاطع فهرست پخش

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

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

function playHelper(bufferNow, bufferLater) {
    var playNow = createSource(bufferNow);
    var source = playNow.source;
    var gainNode = playNow.gainNode;
    var duration = bufferNow.duration;
    var currTime = context.currentTime;
    // Fade the playNow track in.
    gainNode.gain.linearRampToValueAtTime(0, currTime);
    gainNode.gain.linearRampToValueAtTime(1, currTime + ctx.FADE_TIME);
    // Play the playNow track.
    source.noteOn(0);
    // At the end of the track, fade it out.
    gainNode.gain.linearRampToValueAtTime(1, currTime + duration-ctx.FADE_TIME);
    gainNode.gain.linearRampToValueAtTime(0, currTime + duration);
    // Schedule a recursive track change with the tracks swapped.
    var recurse = arguments.callee;
    ctx.timer = setTimeout(function() {
    recurse(bufferLater, bufferNow);
    }, (duration - ctx.FADE_TIME) - 1000);
}

Web Audio API مجموعه مناسبی از روش‌های RampToValue را برای تغییر تدریجی مقدار پارامتر، مانند linearRampToValueAtTime و exponentialRampToValueAtTime ارائه می‌دهد.

While the transition timing function can be picked from built-in linear and exponential ones (as above), you can also specify your own value curve via an array of values using the setValueCurveAtTime function.

اعمال کردن جلوه فیلتر ساده روی صدا

روندنمای ساده‌ای که جریان صدا را از «منبع» به BiquadFilterNode و سپس به «مقصد» نشان می‌دهد.

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

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

انواع فیلترهای پشتیبانی‌شده عبارت‌اند از:

  • فیلتر پایین‌گذر
  • فیلتر بالاگذر
  • فیلتر میان‌گذر
  • فیلتر قفسه پایین
  • فیلتر قفسه بالا
  • فیلتر اوج
  • فیلتر شکافی
  • فیلتر تمام‌گذر

و همه فیلترها شامل پارامترهایی برای مشخص کردن مقداری از بهره، فرکانس اعمال فیلتر، و عامل کیفیت هستند. فیلتر پایین‌گذر محدوده فرکانس پایین‌تر را حفظ می‌کند، اما فرکانس‌های بالا را حذف می‌کند. نقطه قطع با مقدار فرکانس تعیین می‌شود، و ضریب Q بدون واحد است و شکل نمودار را تعیین می‌کند. بهره فقط بر فیلترهای خاصی تأثیر می‌گذارد، مثل فیلترهای قفسه پایین و اوج، و نه این فیلتر پایین‌گذر.

بیایید یک فیلتر پایین‌گذر ساده راه‌اندازی کنیم تا فقط صداهای بم را از یک نمونه صدا استخراج کنیم:

// Create the filter
var filter = context.createBiquadFilter();
// Create the audio graph.
source.connect(filter);
filter.connect(context.destination);
// Create and specify parameters for the low-pass filter.
filter.type = 0; // Low-pass filter. See BiquadFilterNode docs
filter.frequency.value = 440; // Set cutoff to 440 HZ
// Playback the sound.
source.noteOn(0);

به‌طور کلی، کنترل‌های فرکانس باید به‌گونه‌ای تنظیم شوند که در مقیاس لگاریتمی کار کنند، زیرا شنوایی انسان نیز بر اساس همین اصل عمل می‌کند (یعنی A4 برابر با ۴۴۰ هرتز و A5 برابر با ۸۸۰ هرتز است). برای جزئیات بیشتر، تابع FilterSample.changeFrequency را در پیوند کد منبع بالا ببینید.

درنهایت، توجه داشته باشید که کد نمونه به شما امکان می‌دهد فیلتر را متصل و قطع کنید، و نمودار AudioContext را به‌طور پویا تغییر دهید. می‌توانیم با فراخوانی node.disconnect(outputNumber)، اتصال AudioNodes را از نمودار قطع کنیم. برای مثال، برای تغییر مسیر نمودار از عبور از فیلتر به اتصال مستقیم، می‌توانیم کارهای زیر را انجام دهیم:

// Disconnect the source and filter.
source.disconnect(0);
filter.disconnect(0);
// Connect the source directly.
source.connect(context.destination);

گوش کردن بیشتر

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

اگر به‌دنبال الهام گرفتن هستید، توسعه‌دهندگان زیادی قبلاً بااستفاده از میانای برنامه‌سازی کاربردی Web Audio کارهای عالی خلق کرده‌اند. برخی‌از موارد دلخواه من عبارت‌اند از:

  • AudioJedit، ابزار پیوند صدا در مرورگر که از پیوندهای ثابت SoundCloud استفاده می‌کند.
  • ToneCraft، یک توالی‌ساز صدا که در آن صداها با چیدن بلوک‌های سه‌بعدی روی هم ایجاد می‌شوند.
  • Plink، یک بازی مشارکتی ساخت موسیقی بااستفاده از Web Audio و Web Sockets.