简单的 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;
我在使用 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;