Sesli İş Akışı artık varsayılan olarak kullanılabilir

Hongchan Choi

Chrome 64, Web Audio API'de merakla beklenen yeni bir özellikle birlikte geliyor: AudioWorklet. Burada, JavaScript koduyla özel bir ses işlemcisi oluşturmak için gereken kavramları ve kullanımları öğreneceksiniz. Canlı demolara göz atın. Bu serinin bir sonraki makalesi olan İşitsel Worklet Tasarım Deseni, ileri düzey bir işitsel uygulama oluşturmak için ilginizi çekebilir.

Arka plan: ScriptProcessorNode

Web Audio API'deki ses işleme, ana kullanıcı arayüzü iş parçacığından ayrı bir iş parçacığında çalıştığından sorunsuz bir şekilde çalışır. JavaScript'te özel ses işlemeyi etkinleştirmek için Web Audio API, ana kullanıcı arayüzü iş parçacığında kullanıcı komut dosyasını çağırmak üzere etkinlik işleyicileri kullanan bir ScriptProcessorNode önerdi.

Bu tasarımda iki sorun vardır: Etkinlik işleme, tasarım gereği asenkrondur ve kod yürütme ana iş parçacığında gerçekleşir. İlki gecikmeye neden olur, ikincisi ise genellikle çeşitli kullanıcı arayüzü ve DOM ile ilgili görevlerle dolu olan ana iş parçacığına baskı uygular. Bu da kullanıcı arayüzünün "takılmasına" veya sesin "takılmasına" neden olur. Bu temel tasarım hatası nedeniyle ScriptProcessorNode'ün desteği sonlandırıldı ve AudioWorklet ile değiştirildi.

Kavramlar

Ses işleyicisi, kullanıcı tarafından sağlanan JavaScript kodunun tamamını ses işleme iş parçacığında tutar. Bu sayede, sesleri işlemek için ana mesaj dizisine atlamasına gerek kalmaz. Bu, kullanıcı tarafından sağlanan komut dosyası kodunun, diğer yerleşik AudioNodes ile birlikte ses oluşturma iş parçacığında (AudioWorkletGlobalScope) çalışacağı anlamına gelir. Bu da sıfır ek gecikmeye ve eşzamanlı oluşturmaya olanak tanır.

Ana global kapsam ve ses işleyici kapsamı şeması
Şekil 1

Kayıt ve örnek oluşturma

Ses iş parçacığı kullanma işlemi iki bölümden oluşur: AudioWorkletProcessor ve AudioWorkletNode. Bu, ScriptProcessorNode'u kullanmaktan daha karmaşıktır ancak geliştiricilere özel ses işleme için düşük düzeyde bir özellik sunmak için gereklidir. AudioWorkletProcessor, JavaScript kodunda yazılmış gerçek ses işleyiciyi temsil eder ve AudioWorkletGlobalScope içinde bulunur. AudioWorkletNode, AudioWorkletProcessor'un karşılığıdır ve ana mesaj dizisindeki diğer AudioNodes ile olan bağlantıyı yönetir. Ana genel kapsamda gösterilir ve normal bir AudioNode gibi çalışır.

Kayıt ve örneklendirmeyi gösteren bir çift kod snippet'i aşağıda verilmiştir.

// 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 oluşturmak için bir AudioContext nesnesi ve işlemci adını dize olarak eklemeniz gerekir. İşlemci tanımı, yeni Audio Worklet nesnesinin addModule() çağrısı tarafından yüklenebilir ve kaydedilebilir. Ses Workleti dahil Worklet API'leri yalnızca güvenli bir bağlamda kullanılabilir. Bu nedenle, bunları kullanan bir sayfanın HTTPS üzerinden sunulması gerekir. http://localhost, yerel test için güvenli kabul edilir.

AudioWorkletNode sınıfını alt sınıflandırarak iş parçasında çalışan işlemci tarafından desteklenen özel bir düğüm tanımlayabilirsiniz.

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

AudioWorkletGlobalScope içindeki registerProcessor() yöntemi, kaydedilecek işleyicinin adı ve sınıf tanımı için bir dize alır. Genel kapsamda komut dosyası kodu değerlendirmesi tamamlandıktan sonra, AudioWorklet.addModule() tarafından verilen söz çözülerek kullanıcılara sınıf tanımının ana genel kapsamda kullanılmaya hazır olduğu bildirilir.

Özel ses parametreleri

AudioNodes'un kullanışlı özelliklerinden biri, AudioParam ile planlanabilir parametre otomasyonudur. AudioWorkletNodes, ses hızında otomatik olarak kontrol edilebilen parametreleri almak için bunları kullanabilir.

Ses iş parçacığı düğümü ve işlemci şeması
Şekil 2

Kullanıcı tanımlı ses parametreleri, bir AudioParamDescriptor sınıf tanımında AudioParamDescriptor grubu ayarlayarak tanımlanabilir.AudioWorkletProcessor Temel WebAudio motoru, bir AudioWorkletNode oluşturulurken bu bilgileri alır ve ardından AudioParam nesnelerini oluşturup düğüme uygun şekilde bağlar.

/* 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() yöntemi

Gerçek ses işleme, AudioWorkletProcessor içindeki process() geri çağırma yönteminde gerçekleşir. Sınıf tanımında bir kullanıcı tarafından uygulanmalıdır. WebAudio motoru, girişler ve parametreleri beslemek ve çıktıları almak için bu işlevi eşzamanlı olarak çağırır.

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

Ayrıca, geliştiricilerin bellek ayak izini yönetebilmesi için process() yönteminin döndürdüğü değer, AudioWorkletNode nesnesinin ömrünü kontrol etmek amacıyla kullanılabilir. process() yönteminden false döndürülmesi, işleyiciyi etkin olmayan olarak işaretler ve WebAudio motoru artık yöntemi çağırmaz. İşlemcinin etkin kalması için yöntemin true döndürmesi gerekir. Aksi takdirde, düğüm ve işlemci çifti zaman içinde sistem tarafından çöp toplanır.

MessagePort ile iki yönlü iletişim

Bazen özel bir AudioWorkletNode, özel bir filtreyi kontrol etmek için kullanılan dize tabanlı bir type özelliği gibi AudioParam ile eşleşmeyen kontroller göstermek ister. Bu amaç ve daha fazlası için AudioWorkletNode ve AudioWorkletProcessor, iki yönlü iletişim için MessagePort ile donatılmıştır. Bu kanal üzerinden her türlü özel veri alışverişi yapılabilir.

Fig.2
Şekil 2

MessagePort'a hem düğümde hem de işlemcide .port özelliğiyle erişilebilir. Nodun port.postMessage() yöntemi, ilişkili işleyicinin port.onmessage işleyicisine bir mesaj gönderir ve bunun tersi de geçerlidir.

/* 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, veri depolama alanını veya WASM modülünü iş parçacığı sınırı üzerinden aktarmanıza olanak tanıyan aktarılabilir özelliği destekler. Bu, Audio Worklet sisteminin nasıl kullanılabileceği konusunda sayısız olasılık sunar.

Rehber: GainNode oluşturma

AudioWorkletNode ve AudioWorkletProcessor üzerine inşa edilmiş GainNode'un eksiksiz bir örneğini aşağıda bulabilirsiniz.

index.html dosyası:

<!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 dosyası:

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

Bu, ses iş parçacığı sisteminin temellerini kapsar. Canlı demolar Chrome WebAudio ekibinin GitHub deposunda mevcuttur.

Özellik geçişi: Deneysel'den kararlı sürüme

Ses iş parçacığı, Chrome 66 veya sonraki sürümlerde varsayılan olarak etkindir. Chrome 64 ve 65'te bu özellik deneysel işaretin arkasındaydı.