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

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

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

شروع کار با AudioContext

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

یک نمونه از 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، مانند webkitAudioContext از پیشوند webkit استفاده کنید.

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

در حال بارگذاری صداها

Web Audio 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 مراجعه کنید.

هنگامی که داده‌های فایل صوتی (رمز نشده) دریافت شد، می‌توان آن‌ها را برای رمزگشایی بعدی نگه داشت، یا می‌توان بلافاصله با استفاده از روش AudioContext decodeAudioData() رمزگشایی کرد. این روش ArrayBuffer از داده های فایل صوتی ذخیره شده در request.response را می گیرد و به صورت ناهمزمان رمزگشایی می کند (بدون مسدود کردن رشته اصلی اجرای جاوا اسکریپت).

هنگامی که decodeAudioData() به پایان رسید، تابع callback را فراخوانی می کند که داده های صوتی 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);
}

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

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

یک الگوی طبل سنگی ساده
یک الگوی طبل سنگی ساده

که در آن هر نت هشتم یک هیهات پخش می شود و هر ربع به طور متناوب ضربه و تله در زمان 4/4 پخش می شود.

با فرض اینکه بافرهای 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 به مقصد هدایت کنیم تا صدا را دستکاری کنیم:

نمودار صوتی با گره افزایش
نمودار صوتی با گره افزایش

این تنظیم اتصال را می توان به صورت زیر بدست آورد:

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

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

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

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

نمودار صوتی با دو منبع متصل از طریق گره های بهره
نمودار صوتی با دو منبع متصل از طریق گره های بهره

برای تنظیم این، ما به سادگی دو 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
    };
}

متقاطع قدرت برابر

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

متقاطع خطی
متقاطع خطی

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

یک ضربدر قدرت برابر.
یک ضربدر قدرت برابر

متقابل لیست پخش

یکی دیگر از برنامه های متداول crossfader برای یک برنامه پخش کننده موسیقی است. وقتی آهنگی تغییر می‌کند، می‌خواهیم آهنگ فعلی را محو کنیم، و آهنگ جدید را محو کنیم تا از یک انتقال سخت جلوگیری کنیم. برای انجام این کار، یک crossfade را در آینده برنامه ریزی کنید. در حالی که می‌توانیم از 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 ارائه می دهد.

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

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

یک نمودار صوتی با BiquadFilterNode
یک نمودار صوتی با 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 440 هرتز و A5 880 هرتز است). برای جزئیات بیشتر، تابع 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 ، یک ترتیب‌دهنده صدا که در آن صداها با چیدن بلوک‌های سه‌بعدی ایجاد می‌شوند.
  • Plink ، یک بازی ساخت موسیقی مشترک با استفاده از وب صوتی و سوکت های وب.