Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
244 views
in Technique[技术] by (71.8m points)

javascript - Simple WebAudioWorklet Generating Choppy Audio

I'm on Firefox 84.0.1, Windows 10, x86_64. I have a very basic WebAudioWorklet synthesiser that maps keys to the frequencies of musical notes. It is generating very choppy audio when a key is held down. This makes me think that there is not enough audio samples being queued for the speaker to play, hence the audio dropping in and out. However, in audio processing terms, I'm performing a very low-intensive task. As a result, I feel like the default Worklet setup should be able to handle this. Here is my code:

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I think the problem is that currentTime seems to be the only thing which influences the output of your oscillator() function. But currentTime doesn't change during the invocation of the process() function.

I would recommend using currentFrame instead. It will give you an integer value which represents the currentTime in frames. If you combine that with pcm_i you get the actual index of the sample that you're processing.

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...