وارد Audio Worklet شوید

Chrome 64 دارای یک ویژگی جدید مورد انتظار در Web Audio API - AudioWorklet است. این مقاله مفهوم و کاربرد آن را برای کسانی که مشتاق ایجاد یک پردازشگر صوتی سفارشی با کد جاوا اسکریپت هستند معرفی می کند. لطفاً به دموهای زنده در GitHub نگاهی بیندازید. همچنین مقاله بعدی مجموعه، الگوی طراحی Worklet Audio ، ممکن است خواندنی جالب برای ساخت یک برنامه صوتی پیشرفته باشد.

پس زمینه: ScriptProcessorNode

پردازش صدا در Web Audio API در یک رشته مجزا از رشته اصلی UI اجرا می شود، بنابراین به راحتی اجرا می شود. برای فعال کردن پردازش صوتی سفارشی در جاوا اسکریپت، Web Audio API یک ScriptProcessorNode را پیشنهاد کرد که از کنترل‌کننده‌های رویداد برای فراخوانی اسکریپت کاربر در رشته UI اصلی استفاده می‌کرد.

دو مشکل در این طراحی وجود دارد: مدیریت رویداد از نظر طراحی ناهمزمان است و اجرای کد در رشته اصلی اتفاق می‌افتد. اولی تأخیر را القا می‌کند و دومی رشته اصلی را که معمولاً مملو از وظایف مختلف مربوط به UI و DOM است، تحت فشار قرار می‌دهد و باعث می‌شود UI "جنگ" یا صدا "شکلی" ایجاد کند. به دلیل این نقص اساسی طراحی، ScriptProcessorNode از مشخصات منسوخ شده و با AudioWorklet جایگزین شده است.

مفاهیم

Audio Worklet به خوبی کد جاوا اسکریپت ارائه شده توسط کاربر را در رشته پردازش صدا نگه می دارد - یعنی برای پردازش صدا نیازی به پرش به رشته اصلی نیست. این بدان معناست که کد اسکریپت ارائه‌شده توسط کاربر روی رشته رندر صوتی (AudioWorkletGlobalScope) همراه با سایر AudioNode‌های داخلی اجرا می‌شود که تأخیر اضافی و رندر همزمان صفر را تضمین می‌کند.

نمودار دامنه جهانی اصلی و نمودار محدوده Worklet صوتی
عکس. 1

ثبت نام و نمونه سازی

استفاده از Audio Worklet از دو بخش AudioWorkletProcessor و AudioWorkletNode تشکیل شده است. این بیشتر از استفاده از ScriptProcessorNode است، اما برای دادن قابلیت سطح پایین برای پردازش صوتی سفارشی به توسعه دهندگان نیاز است. AudioWorkletProcessor پردازنده صوتی واقعی نوشته شده با کد جاوا اسکریپت را نشان می دهد و در AudioWorkletGlobalScope زندگی می کند. AudioWorkletNode همتای AudioWorkletProcessor است و از اتصال به و از دیگر AudioNode ها در رشته اصلی مراقبت می کند. این در محدوده اصلی جهانی قرار دارد و مانند یک AudioNode معمولی عمل می کند.

در اینجا یک جفت قطعه کد وجود دارد که ثبت و نمونه سازی را نشان می دهد.

// The code in the main global scope.
class MyWorkletNode extends AudioWorkletNode {
  constructor(context) {
    super(context, 'my-worklet-processor');
  }
}

let context = new AudioContext();

context.audioWorklet.addModule('processors.js').then(() => {
  let node = new MyWorkletNode(context);
});

ایجاد یک AudioWorkletNode حداقل به دو چیز نیاز دارد: یک شی AudioContext و نام پردازنده به عنوان یک رشته. تعریف پردازنده را می توان با فراخوانی addModule() شیء جدید Audio Worklet بارگیری و ثبت کرد. APIهای Worklet از جمله Audio Worklet فقط در یک زمینه امن در دسترس هستند، بنابراین صفحه ای که از آنها استفاده می کند باید از طریق HTTPS ارائه شود، اگرچه http://localhost برای آزمایش محلی ایمن در نظر گرفته می شود.

همچنین شایان ذکر است که می‌توانید AudioWorkletNode را برای تعریف یک گره سفارشی که توسط پردازنده در حال اجرا بر روی Worklet پشتیبانی می‌شود، زیر کلاس قرار دهید.

// This is "processor.js" file, evaluated in AudioWorkletGlobalScope upon
// audioWorklet.addModule() call in the main global scope.
class MyWorkletProcessor extends AudioWorkletProcessor {
  constructor() {
    super();
  }

  process(inputs, outputs, parameters) {
    // audio processing code here.
  }
}

registerProcessor('my-worklet-processor', MyWorkletProcessor);

متد registerProcessor() در AudioWorkletGlobalScope یک رشته برای نام پردازنده و تعریف کلاس می گیرد. پس از تکمیل ارزیابی کد اسکریپت در حوزه جهانی، وعده AudioWorklet.addModule() حل می شود و به کاربران اطلاع می دهد که تعریف کلاس آماده استفاده در حوزه جهانی اصلی است.

AudioParam سفارشی

یکی از موارد مفید در مورد AudioNodes، اتوماسیون پارامترهای زمانبندی شده با AudioParams است. AudioWorkletNodes می‌تواند از این پارامترها برای دریافت پارامترهایی استفاده کند که می‌توانند به صورت خودکار با نرخ صدا کنترل شوند.

نمودار گره کارگاه صوتی و پردازنده
شکل 2

AudioParam های تعریف شده توسط کاربر را می توان در تعریف کلاس AudioWorkletProcessor با تنظیم مجموعه ای از AudioParamDescriptors اعلام کرد. موتور WebAudio زیربنایی این اطلاعات را پس از ساخت یک AudioWorkletNode دریافت می‌کند و سپس اشیاء AudioParam را بر این اساس ایجاد و به گره پیوند می‌دهد.

/* A separate script file, like "my-worklet-processor.js" */
class MyWorkletProcessor extends AudioWorkletProcessor {

  // Static getter to define AudioParam objects in this custom processor.
  static get parameterDescriptors() {
    return [{
      name: 'myParam',
      defaultValue: 0.707
    }];
  }

  constructor() { super(); }

  process(inputs, outputs, parameters) {
    // |myParamValues| is a Float32Array of either 1 or 128 audio samples
    // calculated by WebAudio engine from regular AudioParam operations.
    // (automation methods, setter) Without any AudioParam change, this array
    // would be a single value of 0.707.
    const myParamValues = parameters.myParam;

    if (myParamValues.length === 1) {
      // |myParam| has been a constant value for the current render quantum,
      // which can be accessed by |myParamValues[0]|.
    } else {
      // |myParam| has been changed and |myParamValues| has 128 values.
    }
  }
}

متد AudioWorkletProcessor.process()

پردازش واقعی صدا در process() callback در AudioWorkletProcessor اتفاق می افتد و باید توسط کاربر در تعریف کلاس پیاده سازی شود. موتور WebAudio این تابع را به صورت هم زمان برای تغذیه ورودی ها و پارامترها و واکشی خروجی ها فراخوانی می کند.

/* AudioWorkletProcessor.process() method */
process(inputs, outputs, parameters) {
  // The processor may have multiple inputs and outputs. Get the first input and
  // output.
  const input = inputs[0];
  const output = outputs[0];

  // Each input or output may have multiple channels. Get the first channel.
  const inputChannel0 = input[0];
  const outputChannel0 = output[0];

  // Get the parameter value array.
  const myParamValues = parameters.myParam;

  // if |myParam| has been a constant value during this render quantum, the
  // length of the array would be 1.
  if (myParamValues.length === 1) {
    // Simple gain (multiplication) processing over a render quantum
    // (128 samples). This processor only supports the mono channel.
    for (let i = 0; i < inputChannel0.length; ++i) {
      outputChannel0[i] = inputChannel0[i] * myParamValues[0];
    }
  } else {
    for (let i = 0; i < inputChannel0.length; ++i) {
      outputChannel0[i] = inputChannel0[i] * myParamValues[i];
    }
  }

  // To keep this processor alive.
  return true;
}

علاوه بر این، مقدار بازگشتی process() را می توان برای کنترل طول عمر AudioWorkletNode استفاده کرد تا توسعه دهندگان بتوانند ردپای حافظه را مدیریت کنند. برگرداندن متد false from process() باعث غیرفعال بودن پردازنده می شود و موتور WebAudio دیگر متد را فراخوانی نمی کند. برای زنده نگه داشتن پردازنده، متد باید true باشد. در غیر این صورت، جفت گره/پردازنده در نهایت توسط سیستم جمع‌آوری می‌شود.

ارتباط دو طرفه با MessagePort

گاهی اوقات AudioWorkletNodes سفارشی می‌خواهند کنترل‌هایی را که به AudioParam نگاشت نمی‌شوند در معرض دید قرار دهند. به عنوان مثال، یک ویژگی type مبتنی بر رشته می تواند برای کنترل یک فیلتر سفارشی استفاده شود. برای این منظور و فراتر از آن، AudioWorkletNode و AudioWorkletProcessor مجهز به MessagePort برای ارتباط دوطرفه هستند. هر نوع داده سفارشی از طریق این کانال قابل تبادل است.

شکل 2
شکل 2

MessagePort را می توان از طریق ویژگی .port در هر دو گره و پردازنده دسترسی داشت. متد port.postMessage() گره پیامی را به handler port.onmessage پردازنده مرتبط می‌فرستد و بالعکس.

/* The code in the main global scope. */
context.audioWorklet.addModule('processors.js').then(() => {
  let node = new AudioWorkletNode(context, 'port-processor');
  node.port.onmessage = (event) => {
    // Handling data from the processor.
    console.log(event.data);
  };

  node.port.postMessage('Hello!');
});
/* "processor.js" file. */
class PortProcessor extends AudioWorkletProcessor {
  constructor() {
    super();
    this.port.onmessage = (event) => {
      // Handling data from the node.
      console.log(event.data);
    };

    this.port.postMessage('Hi!');
  }

  process(inputs, outputs, parameters) {
    // Do nothing, producing silent output.
    return true;
  }
}

registerProcessor('port-processor', PortProcessor);

همچنین توجه داشته باشید که MessagePort از Transferable پشتیبانی می‌کند، که به شما امکان می‌دهد ذخیره‌سازی داده یا یک ماژول WASM را بر روی مرز رشته انتقال دهید. این امکان بی شماری را در مورد نحوه استفاده از سیستم Audio Worklet باز می کند.

راه حل: ساخت GainNode

با کنار هم قرار دادن همه چیز، در اینجا یک مثال کامل از GainNode است که در بالای AudioWorkletNode و AudioWorkletProcessor ساخته شده است.

Index.html

<!doctype html>
<html>
<script>
  const context = new AudioContext();

  // Loads module script via AudioWorklet.
  context.audioWorklet.addModule('gain-processor.js').then(() => {
    let oscillator = new OscillatorNode(context);

    // After the resolution of module loading, an AudioWorkletNode can be
    // constructed.
    let gainWorkletNode = new AudioWorkletNode(context, 'gain-processor');

    // AudioWorkletNode can be interoperable with other native AudioNodes.
    oscillator.connect(gainWorkletNode).connect(context.destination);
    oscillator.start();
  });
</script>
</html>

gain-processor.js

class GainProcessor extends AudioWorkletProcessor {

  // Custom AudioParams can be defined with this static getter.
  static get parameterDescriptors() {
    return [{ name: 'gain', defaultValue: 1 }];
  }

  constructor() {
    // The super constructor call is required.
    super();
  }

  process(inputs, outputs, parameters) {
    const input = inputs[0];
    const output = outputs[0];
    const gain = parameters.gain;
    for (let channel = 0; channel < input.length; ++channel) {
      const inputChannel = input[channel];
      const outputChannel = output[channel];
      if (gain.length === 1) {
        for (let i = 0; i < inputChannel.length; ++i)
          outputChannel[i] = inputChannel[i] * gain[0];
      } else {
        for (let i = 0; i < inputChannel.length; ++i)
          outputChannel[i] = inputChannel[i] * gain[i];
      }
    }

    return true;
  }
}

registerProcessor('gain-processor', GainProcessor);

این اصول اساسی سیستم Audio Worklet را پوشش می دهد. دموهای زنده در مخزن GitHub تیم Chrome WebAudio موجود است.

انتقال ویژگی: آزمایشی به پایدار

Audio Worklet به طور پیش فرض برای Chrome 66 یا جدیدتر فعال است. در کروم 64 و 65، این ویژگی پشت پرچم آزمایشی قرار داشت.