ऑडियो वर्कलेट डालें

Chrome 64 में, Web Audio API - AudioWorklet में मौजूद सुविधा है. इस सुविधा का लंबे समय से इंतज़ार किया जा रहा है. इस लेख में, इसके सिद्धांत और इसके इस्तेमाल के बारे में उन लोगों के लिए बताया गया है जो JavaScript कोड की मदद से कस्टम ऑडियो प्रोसेसर बनाना चाहते हैं. कृपया GitHub पर लाइव डेमो पर नज़र डालें. साथ ही, सीरीज़ का अगला लेख, ऑडियो वर्कलेट डिज़ाइन पैटर्न भी हो सकता है जो बेहतर ऑडियो ऐप्लिकेशन बनाने के लिए, पढ़ने में दिलचस्पी हो.

बैकग्राउंड: ScriptProcessorNode

Web Audio API में ऑडियो प्रोसेसिंग, मुख्य यूज़र इंटरफ़ेस (यूआई) थ्रेड से अलग थ्रेड में चलती है. इसलिए, यह आसानी से चलती है. JavaScript में कस्टम ऑडियो प्रोसेसिंग चालू करने के लिए, Web Audio API ने एक ScriptProcessorNode का सुझाव दिया. इसमें इवेंट हैंडलर का इस्तेमाल करके, मुख्य यूज़र इंटरफ़ेस (यूआई) थ्रेड में उपयोगकर्ता स्क्रिप्ट को शुरू किया गया.

इस डिज़ाइन में दो समस्याएं हैं: डिज़ाइन के हिसाब से इवेंट हैंडलिंग, एसिंक्रोनस होती है और कोड एक्ज़ीक्यूशन मुख्य थ्रेड पर होता है. पिछली लाइन, इंतज़ार के समय को शुरू करती है और बाद वाली मुख्य थ्रेड उस मुख्य थ्रेड पर दबाव डालती है जिसमें आम तौर पर कई यूज़र इंटरफ़ेस (यूआई) और डीओएम से जुड़े टास्क होते हैं. इसकी वजह से या तो यूज़र इंटरफ़ेस (यूआई) "जैंक" या ऑडियो में "ग्लिच" हो सकता है. डिज़ाइन के इस बुनियादी नुकसान की वजह से, ScriptProcessorNode को स्पेसिफ़िकेशन से हटा दिया गया है और इसे AudioWorklet से बदल दिया गया है.

कॉन्सेप्ट

ऑडियो वर्कलेट, उपयोगकर्ता से मिलने वाले JavaScript कोड को ऑडियो प्रोसेसिंग थ्रेड में अच्छी तरह से रखता है. इसका मतलब है कि ऑडियो प्रोसेस करने के लिए, इसे मुख्य थ्रेड पर जाने की ज़रूरत नहीं होती. इसका मतलब है कि उपयोगकर्ता से मिला स्क्रिप्ट कोड, पहले से मौजूद दूसरे AudioNodes के साथ-साथ ऑडियो रेंडरिंग थ्रेड (AudioWorkletGlobalScope) पर चलता है. इससे यह पक्का होता है कि किसी भी कार्रवाई को पूरा होने में लगने वाला समय और सिंक्रोनस रेंडरिंग शून्य हो.

मुख्य ग्लोबल स्कोप और ऑडियो वर्कलेट के स्कोप का डायग्राम
इमेज1

रजिस्ट्रेशन और इंस्टैंशिएट करना

ऑडियो वर्कलेट का इस्तेमाल करने के दो हिस्से होते हैं: AudioWorkletProcessor और AudioWorkletNode. स्क्रिप्ट प्रोसेसरनोड का इस्तेमाल करने के बजाय, यह प्रोसेस ज़्यादा अहम है, लेकिन इसकी ज़रूरत डेवलपर को कस्टम ऑडियो प्रोसेसिंग की कम-लेवल वाली सुविधा देने के लिए होती है. AudioWorkletProcessor, JavaScript कोड में लिखे गए असल ऑडियो प्रोसेसर को दिखाता है और यह AudioWorkletGlobalScope में मौजूद होता है. AudioWorkletNode, AudioWorkletProcessor का हिस्सा है. यह मुख्य थ्रेड में दूसरे AudioNodes के साथ कनेक्ट होने और कनेक्शन का काम करने में मदद करता है. यह मुख्य ग्लोबल स्कोप और सामान्य 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() कॉल से लोड और रजिस्टर किया जा सकता है. ऑडियो वर्कलेट के साथ-साथ वर्कलेट एपीआई सिर्फ़ सुरक्षित कॉन्टेक्स्ट में उपलब्ध होते हैं. इसलिए, उनका इस्तेमाल करने वाला पेज एचटीटीपीएस पर दिखाया जाना चाहिए. हालांकि, स्थानीय जांच के लिए http://localhost को सुरक्षित माना जाता है.

यह भी ध्यान रखें कि आपके पास AudioWorkletNode को सब-क्लास करने का विकल्प है, ताकि हम वर्कलेट पर चल रहे प्रोसेसर से बने कस्टम नोड को तय कर सकें.

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

AudioWorkletGlobalScope में registerProcessor() तरीके में, प्रोसेसर के नाम को रजिस्टर करने और क्लास की परिभाषा के लिए एक स्ट्रिंग होती है. ग्लोबल स्कोप में स्क्रिप्ट कोड की जांच पूरी होने के बाद, AudioWorklet.addModule() का प्रॉमिस, उपयोगकर्ताओं को यह सूचना देकर पूरा किया जाएगा कि क्लास डेफ़िनिशन का इस्तेमाल मुख्य ग्लोबल स्कोप में किया जा सकता है.

आपकी पसंद के मुताबिक ऑडियो पैरामीटर

AudioParams के साथ शेड्यूल किए जा सकने वाले पैरामीटर ऑटोमेशन की वजह से भी ऐसा किया जा सकता है. AudioWorkletNodes एक्सपोज़ किए गए पैरामीटर पाने के लिए इनका इस्तेमाल कर सकते हैं, जिन्हें ऑडियो दर से अपने-आप कंट्रोल किया जा सकता है.

ऑडियो वर्कलेट नोड और प्रोसेसर डायग्राम
इमेज 2

उपयोगकर्ता के तय किए गए AudioParams का एलान, AudioParamDescriptors का सेट सेट अप करके, AudioWorkletProcessor क्लास में होना चाहिए. नीचे दिया गया 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() तरीका

असल ऑडियो प्रोसेसिंग, AudioWorkletProcessor में मौजूद process() कॉलबैक तरीके में होती है. साथ ही, उपयोगकर्ता को इसे क्लास परिभाषा में लागू करना चाहिए. 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 की लाइफ़टाइम वैल्यू को कंट्रोल करने के लिए किया जा सकता है. इससे डेवलपर, मेमोरी फ़ुटप्रिंट को मैनेज कर सकते हैं. process() तरीके से false वापस करने पर, यह मार्क कर देगा कि प्रोसेसर काम नहीं कर रहा है और WebAudio इंजन अब इस तरीके को शुरू नहीं करेगा. प्रोसेसर को चालू रखने के लिए, तरीके को true के तौर पर सेट करना होगा. ऐसा न करने पर, नोड/प्रोसेसर की जोड़ी रद्द हो जाएगी और उसे सिस्टम की ओर से इकट्ठा किया जाएगा.

MessagePort के साथ दो-तरफ़ा बातचीत

कभी-कभी कस्टम AudioWorkletNodes ऐसे कंट्रोल दिखाना चाहेगा जो AudioParam से मैप न करते हों. उदाहरण के लिए, कस्टम फ़िल्टर को कंट्रोल करने के लिए, स्ट्रिंग पर आधारित type एट्रिब्यूट का इस्तेमाल किया जा सकता है. इस काम के लिए और इसके अलावा, AudioWorkletNode और AudioWorkletProcessor में दोनों तरफ़ से कम-ज़्यादा होने की सुविधा के लिए MessagePort में मौजूद हैं. इस चैनल के ज़रिए किसी भी तरह के कस्टम डेटा को बदला जा सकता है.

Fig.2
इमेज 2

MessagePort को नोड और प्रोसेसर, दोनों पर .port एट्रिब्यूट से ऐक्सेस किया जा सकता है. नोड का port.postMessage() तरीका, संबंधित प्रोसेसर के port.onmessage हैंडलर को मैसेज भेजता है. इसी तरह, नोड से जुड़े प्रोसेसर के 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 इस्तेमाल करने पर, ट्रांसफ़रेबल की सुविधा काम करती है. इससे थ्रेड की सीमा पर डेटा स्टोरेज या WASM मॉड्यूल ट्रांसफ़र करने की सुविधा मिलती है. इससे इस बात की अनगिनत संभावना मिलती है कि ऑडियो वर्कलेट सिस्टम का इस्तेमाल कैसे किया जा सकता है.

सिलसिलेवार तरीके से निर्देश: GainNode बनाना

पूरी जानकारी को एक साथ रखते हुए, यहां AudioWorkletNode और AudioWorkletProcessor पर बने GainNode का उदाहरण दिया गया है.

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>

लाभ प्रोसेसर.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);

इसमें ऑडियो वर्कलेट सिस्टम की बुनियादी जानकारी दी गई है. लाइव डेमो Chrome WebAudio टीम के GitHub रिपॉज़िटरी पर उपलब्ध हैं.

सुविधा में बदलाव: एक्सपेरिमेंट के तौर पर स्टेबल वर्शन पर स्विच किया गया

Chrome 66 या इसके बाद के वर्शन के लिए, ऑडियो वर्कलेट डिफ़ॉल्ट रूप से चालू होता है. Chrome 64 और 65 में, इस सुविधा का इस्तेमाल प्रयोग के तौर पर किए जाने वाले फ़्लैग के पीछे किया गया था.