worklet של האודיו זמין עכשיו כברירת מחדל

Hongchan Choi

Chrome 64 כולל תכונה חדשה שכולם מצפים לה ב-Web Audio API – AudioWorklet. כאן תלמדו מושגים ואת אופן השימוש כדי ליצור מעבד אודיו מותאם אישית עם קוד JavaScript. כדאי לעיין הדגמות בשידור חי. במאמר הבא בסדרה, תבנית עיצוב Worklet של אודיו, יכול להיות מאמר מעניין שיעזור לכם לפתח אפליקציית אודיו מתקדמת.

רקע: ScriptProcessorNode

עיבוד האודיו ב-Web Audio API פועל בשרשור נפרד שרשור בממשק המשתמש, כך שהוא פועל בצורה חלקה. כדי להפעיל עיבוד אודיו בהתאמה אישית ב: JavaScript, ה-Web Audio API הציע ScriptProcessorNode שהשתמש בו גורמים מטפלים באירועים כדי להפעיל סקריפט של משתמש בשרשור הראשי של ממשק המשתמש.

יש שתי בעיות בעיצוב הזה: הטיפול באירועים הוא אסינכרוני בפועל, והרצת הקוד מתבצעת בשרשור הראשי. הקודם קובע את זמן האחזור, והשני לוחץ על ה-thread הראשי עמוסים בדרך כלל במשימות שונות הקשורות לממשק המשתמש ול-DOM, שגורמות לכל ממשק משתמש ל-'jank' או אודיו ל"תקלה". בגלל הפגם הבסיסי הזה בעיצוב, ההוראה ScriptProcessorNode הוצאה משימוש מהמפרט הוחלף ב-AudioWorklet.

מושגים

worklet של האודיו שומר את קוד ה-JavaScript שסופק על ידי המשתמש שרשור לעיבוד אודיו. כלומר לא צריך לדלג לעיבוד אודיו. המשמעות היא שקוד הסקריפט שסופק על ידי המשתמש יכול לרוץ בשרשור של רינדור אודיו (AudioWorkletGlobalScope) יחד עם AudioNodes מובנה, שמבטיח אפס זמן אחזור נוסף וסינכרוני ברינדור.

תרשים ההיקף הגלובלי הראשי ותרשים ההיקף של ה-Worklet של האודיו
Fig.1

רישום ויצירה

השימוש ב-Worklet של אודיו מורכב משני חלקים: AudioWorkletProcessor ו AudioWorkletNode. התהליך הזה כרוך יותר בשימוש ב-ScriptProcessorNode, אבל הוא צריך כדי לתת למפתחים את היכולת ברמה הנמוכה ליצור אודיו מותאם אישית בעיבוד. AudioWorkletProcessor מייצג את מעבד האודיו בפועל כתוב בקוד JavaScript, והוא נמצא ב-AudioWorkletGlobalScope. AudioWorkletNode היא המקבילה ל-AudioWorkletProcessor, והיא לוקחת בחיבור אל AudioNodes אחר ב-thread הראשי וממנו. הוא נחשפת בהיקף הגלובלי הראשי ומתפקדת כמו 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 באובייקט Audio Worklet. ממשקי API של worklet, כולל Audio worklet, זמינים רק הקשר מאובטח, דף שמשתמש בהם חייב להיות מוצג באמצעות HTTPS, למרות ש-http://localhost שנחשבות בטוחות לבדיקות מקומיות.

אפשר לתת מחלקה משנית ל-AudioWorkletNode כדי להגדיר צומת מותאם אישית שמגובה על ידי המעבד שפועל ב-worklet.

// This is the "processors.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() תבוטל וההודעה למשתמשים תבוטל שהגדרת הכיתה מוכנה לשימוש בהיקף הגלובלי הראשי.

פרמטרים מותאמים אישית של אודיו

אחד היתרונות השימושיים ב-AudioNodes הוא פרמטר שניתן לתזמן. אוטומטי באמצעות AudioParam. AudioWorkletNodes יכולים להשתמש בהם כדי לקבל פרמטרים חשופים שניתן לשלוט בהם בקצב האודיו באופן אוטומטי.

תרשים מעבד וצומת של worklet של אודיו
Fig.2

אפשר להצהיר על פרמטרים של אודיו שהוגדרו על ידי המשתמש בAudioWorkletProcessor הגדרת הכיתה על ידי הגדרת קבוצה של AudioParamDescriptor. המנוע 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() 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 מ-process() סימני method שהמעבד לא פעיל, והמנוע WebAudio לא מפעיל יותר . כדי שהמעבד יישאר פעיל, השיטה חייבת להחזיר true. אחרת, הצמד של הצומת והמעבד הוא אשפה שנאספת על ידי המערכת בסופו של דבר.

תקשורת דו-כיוונית עם MessagePort

לפעמים, AudioWorkletNode בהתאמה אישית רוצה לחשוף פקדים שלא למפות ל-AudioParam, למשל למאפיין type שמבוסס על מחרוזות שמשמש לשליטה במסנן מותאם אישית. למטרה הזו ואחריה, AudioWorkletNode ו-AudioWorkletProcessor כוללים MessagePort לתקשורת דו-כיוונית. כל סוג של נתונים בהתאמה אישית ניתן להחליף ביניהם דרך הערוץ הזה.

Fig.2
Fig.2

אפשר לגשת ל-MessagePort באמצעות המאפיין .port גם בצומת וגם במעבד. רכיב ה-method 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!');
});
/* "processors.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 דרך גבולות ה-thread. תיפתח יש אינספור אפשרויות לאופן השימוש במערכת Audio Worklet.

הדרכה מפורטת: בניית ChangeNode

הנה דוגמה מלאה לשימוש ב- מקבלים GetNode, שמבוסס על AudioWorkletNode וגם AudioWorkletProcessor

הקובץ index.html:

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

  // Loads module script with 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.

מעבר תכונה: מניסיוני ליציב

worklet של האודיו מופעל כברירת מחדל ב-Chrome 66 ואילך. בגרסאות 64 ו-65 של Chrome, התכונה עמדה מאחורי הדגל של הניסוי.