简单的 WebAudioWorklet 生成断断续续的音频

Simple WebAudioWorklet Generating Choppy Audio

我在使用 Firefox 84.0.1,Windows 10,x86_64。我有一个非常基本的 WebAudioWorklet 合成器,可以将按键映射到音符的频率。当按下一个键时,它会产生非常不稳定的音频。这让我觉得没有足够的音频样本排队等待扬声器播放,因此音频进进出出。但是,就音频处理而言,我执行的任务强度非常低。因此,我觉得默认的 Worklet 设置应该能够处理这个问题。这是我的代码:

syn.js

(async() => {
  let a2_hertz = 110.0;
  let twelfth_root_of_two = Math.pow(2.0, 1.0 / 12.0);
  
  let audio_cxt = new AudioContext();
  await audio_cxt.audioWorklet.addModule("syn-worklet.js", {credentials: "omit"});
 
  let audio_worklet_options = {
    numberOfInputs: 0,
    numberOfOutputs: 1,
    outputChannelCount: [audio_cxt.destination.channelCount]
  };
  let audio_worklet = new AudioWorkletNode(audio_cxt, "synthesiser", audio_worklet_options);
  audio_worklet.connect(audio_cxt.destination);
  
  document.addEventListener("keydown", (evt) => {
    for (let key = 0; key < 12; ++key) {
      if (evt.code == "Key" + "QWERTYUIOPAS"[key]) {
        audio_worklet.port.postMessage(a2_hertz * Math.pow(twelfth_root_of_two, key));
      }
    }
  });
  
  document.addEventListener("keyup", (evt) => {
    audio_worklet.port.postMessage(0.0);
  });
})();

syn-worklet.js

function angular_frequency(hertz) {
  return hertz * 2 * Math.PI;
}

let OSC_TYPES = {"sine": 0, "square": 1, "triangle": 2};
function oscillator(hertz, osc_type) {
  switch (osc_type) {
    case OSC_TYPES.sine: {
      return Math.sin(angular_frequency(hertz) * currentTime);
    } break;
    case OSC_TYPES.square: {
      return Math.sin(angular_frequency(hertz) * currentTime) > 0.0 ? 1.0 : -1.0;
    } break;
    case OSC_TYPES.triangle: {
      return Math.asin(Math.sin(angular_frequency(hertz) * currentTime)) * (2.0 / Math.PI);
    } break;
    default: {
      return 0.0;
    }
  }
}

class Synthesiser extends AudioWorkletProcessor {
  constructor() {
    super();

    this.hertz = 0.0;

    this.port.onmessage = (evt) => {
      this.hertz = evt.data;
    };
  }

  process(inputs, outputs) {
    let channels = outputs[0];
    let num_samples_per_channel = channels[0].length;

    for (let pcm_i = 0; pcm_i < num_samples_per_channel; ++pcm_i) {
      let volume = 0.1;
      let pcm_value = volume * oscillator(this.hertz, OSC_TYPES.sine);
      for (let channel_i = 0; channel_i < channels.length; ++channel_i) {
        channels[channel_i][pcm_i] = pcm_value;
      }
    }

    return true;
  }
}

registerProcessor("synthesiser", Synthesiser);

我认为问题在于 currentTime 似乎是唯一影响 oscillator() 函数输出的因素。但是 currentTime 在调用 process() 函数期间不会改变。

我建议改用 currentFrame。它会给你一个整数值,代表 currentTime 帧。如果将其与 pcm_i 结合使用,您将获得正在处理的样本的实际索引。

const currentSample = currentFrame + pcm_i;
const currentSampleInSeconds = (currentFrame + pcm_i) / sampleRate;