用 AudioWorklet 来做白噪声
Posted on Thu 30 September 2021 in Journal
AudioNode 是什么
在 WebRTC 中 AudioNode 表示一个音频处理模块。它可以是用来表示一个音频源,一个音频目标,或者中间处理模块(例如 BiquadFilterNode , ConvolverNode, 或 GainNode)
每一个 AudioNode 都有一个输入和输出,多个音频节点连接在一起来构建一个音频处理图。这个图包含在一个 AudioContext , 每一个 AudioNode 只能隶属于一个 AudioContext.
源节点有零个输入但有一个或多个输出,可用于生成声音。另一方面,目标节点没有输出;相反,它的所有输入都直接在扬声器(或音频上下文使用的任何音频输出设备)上播放。此外,还有具有输入和输出的处理节点。完成的确切处理因一个 AudioNode 而异,但通常,一个节点读取其输入,进行一些与音频相关的处理,并为其输出生成新值,或让音频通过(例如在 AnalyserNode 中,其中处理的结果单独访问)。
图中的节点越多,延迟就越高。例如,如果您的图形有 500 毫秒的延迟,那么当源节点播放声音时,需要半秒时间才能在您的扬声器上听到该声音(或者甚至更长,因为底层音频设备的延迟)。因此,如果您需要具有交互式音频,请保持图形尽可能小,并将用户控制的音频节点放在图形的末尾。例如,音量控制 (GainNode) 应该是最后一个节点,以便音量更改立即生效。
每个输入和输出都有给定数量的通道。例如,单声道音频有一个通道,而立体声音频有两个通道。 Web Audio API 将根据需要对通道数量进行上混或下混;有关详细信息,请查看网络音频规范。
AudioWorklet 是什么
Web Audio API 的 AudioWorkletNode 接口代表用户定义的 AudioNode 的基类,它可以与其他节点一起连接到音频路由图。 它有一个关联的 AudioWorkletProcessor,它在 Web 音频渲染线程中进行实际的音频处理。
以一个白噪声生成器 NoiseGenerator 为例
Example 1: Noise generator node
- noise-generator.html
const context = new AudioContext();
const demoCode = async (context) => {
await context.audioWorklet.addModule('noise-generator.js');
const modulator = new OscillatorNode(context);
const modGain = new GainNode(context);
const noiseGenerator = new AudioWorkletNode(context, 'noise-generator');
noiseGenerator.connect(context.destination);
// Connect the oscillator to 'amplitude' AudioParam.
const paramAmp = noiseGenerator.parameters.get('amplitude');
modulator.connect(modGain).connect(paramAmp);
modulator.frequency.value = 0.5;
modGain.gain.value = 0.75;
modulator.start();
};
}
document.getElementById("startButton").addEventListener("click", demoNode);
- NoiseGenerator: noise-generator.js
**
* A noise generator with a gain AudioParam.
*
* @class NoiseGenerator
* @extends AudioWorkletProcessor
*/
class NoiseGenerator extends AudioWorkletProcessor {
static get parameterDescriptors() {
return [{name: 'amplitude', defaultValue: 0.25, minValue: 0, maxValue: 1}];
}
process(inputs, outputs, parameters) {
const output = outputs[0];
const amplitude = parameters.amplitude;
const isAmplitudeConstant = amplitude.length === 1;
for (let channel = 0; channel < output.length; ++channel) {
const outputChannel = output[channel];
for (let i = 0; i < outputChannel.length; ++i) {
// This loop can branch out based on AudioParam array length, but
// here we took a simple approach for the demonstration purpose.
outputChannel[i] = 2 * (Math.random() - 0.5) *
(isAmplitudeConstant ? amplitude[0] : amplitude[i]);
}
}
return true;
}
}
registerProcessor('noise-generator', NoiseGenerator);
Example 2: Gain Node
- gain-processor.html
<!doctype html>
<html>
<script>
const context = new AudioContext();
// Loads module script via 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);