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

بوریس اسموس
Boris Smus

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

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

شروع کار با AudioContext

یک AudioContext برای مدیریت و پخش همه صداها است. برای تولید صدا با استفاده از Web Audio API، یک یا چند منبع صدا ایجاد کنید و آنها را به مقصد صدا که توسط نمونه AudioContext ارائه شده است، متصل کنید. این اتصال نیازی به مستقیم بودن ندارد و می‌تواند از طریق هر تعداد AudioNode واسطه که به عنوان ماژول‌های پردازش سیگنال صوتی عمل می‌کنند، انجام شود. این مسیریابی با جزئیات بیشتر در مشخصات 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 مانند ایجاد AudioNodeها و رمزگشایی داده‌های فایل صوتی، متدهای AudioContext هستند.

بارگیری صداها

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

این API از بارگذاری داده‌های فایل صوتی در قالب‌های مختلف، مانند 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 مراجعه کنید.

پس از دریافت داده‌های فایل صوتی (رمزگشایی نشده)، می‌توان آن را برای رمزگشایی بعدی نگه داشت، یا می‌توان آن را بلافاصله با استفاده از متد decodeAudioData() در AudioContext رمزگشایی کرد. این متد 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) زمان‌بندی دقیق پخش صدا برای بازی‌ها و سایر برنامه‌های حساس به زمان را آسان می‌کند. با این حال، برای اینکه این زمان‌بندی به درستی کار کند، مطمئن شوید که بافرهای صدای شما از قبل بارگذاری شده‌اند.

خلاصه‌سازی 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);
}

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

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

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

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

با فرض اینکه بافرهای kick ، snare و hihat را بارگذاری کرده‌ایم، کد لازم برای انجام این کار ساده است:

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;

محو شدن متقاطع بین دو صدا

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

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

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

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

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 برای انجام این برنامه‌ریزی استفاده کنیم، این دقیق نیست . با 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);
}

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

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

اعمال یک افکت فیلتر ساده به یک صدا

فلوچارت ساده‌ای که جریان صدا از منبع به 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 را تغییر می‌دهد. می‌توانیم AudioNodes را با فراخوانی node.disconnect(outputNumber) از نمودار جدا کنیم. به عنوان مثال، برای تغییر مسیر نمودار از عبور از یک فیلتر به یک اتصال مستقیم، می‌توانیم موارد زیر را انجام دهیم:

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

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

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

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

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