虽然目前被接受的答案可行,但有一种更好的方法可以做到这一点,基于振荡器不是“声源”,而是信号源的理解,而“获得声音”的最佳方法不是仅在需要声音时启动(一个或多个)振荡器,而是让它们已经运行,并根据需要简单地允许或阻止它们的信号。
因此,你真正想要做的是控制信号:如果你让它通过,并将其连接到音频输出,我们就会听到它,如果你阻止它,我们就听不到它。所以,即使你认为使用增益节点是“不良实践”,那也完全相反。我们绝对要使用增益节点:
信号→音量控制→音频输出
在这个链条中,我们可以让信号永远运行(因为它应该这样),而可以使用音量控制来控制播放。例如,假设我们想在点击按钮时播放440Hz的蜂鸣声。我们首先设置我们的链,只需一次:
const audioContext = new AudioContext();
const gainNode = audioContext.createGain();
gainNode.connect(audioContext.destination);
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
const osc = audioContext.createOscillator();
osc.frequency.value = 440;
osc.connect(gainNode);
osc.start();
然后为了播放提示音,我们使用setTargetAtTime函数将音量设置为1,该函数允许我们在“某个特定的时间”更改参数,并且通常会在一个(通常很短的)时间间隔内平滑地从“当前数值”转换为“目标数值”,以避免当我们使用setValueAtTime
时出现的噼啪声和爆裂声。在那种情况下,信号几乎肯定不会在我们设置音量的确切时刻为零,所以扬声器必须跳到新位置,产生这些可爱的裂缝声。我们不想要那些。
这也意味着我们不会构建任何新元素或发生任何分配或垃圾回收开销:我们仅仅设置控制最终传输到音频终点的信号类型的值:
const smoothingInterval = 0.02;
const beepLengthInSeconds = 0.5;
playButton.addEventListener(`click`, () => {
const now = audioContext.currentTime;
gainNode.gain.setTargetAtTime(1, now, smoothingInterval);
gainNode.gain.setTargetAtTime(0, now + beepLengthInSeconds, smoothingInterval);
});
我们完成了。振荡器一直在运行,就像实际的声音电路一样,在此过程中几乎不使用资源,我们通过切换音量来控制是否可以听到它。
当然,我们可以通过将该链封装在具有自己的play()
函数的东西中,使其变得更加有用:
const audioContext = new AudioContext();
const now = () => audioContext.currentTime;
const smoothingInterval = 0.02;
const beepLengthInSeconds = 0.5;
const beeps = [220,440,880].map(Hz => createBeeper(Hz));
playButton.addEventListener(`click`, () => {
const note = (beeps.length * Math.random()) | 0;
beeps[note].play();
});
function createBeeper(Hz=220, duration=beepLengthInSeconds) {
const gainNode = audioContext.createGain();
gainNode.connect(audioContext.destination);
gainNode.gain.setValueAtTime(0, now());
const osc = audioContext.createOscillator();
osc.frequency.value = Hz;
osc.connect(gainNode);
osc.start();
return {
play: (howLong=duration) => {
console.log(`playing ${Hz}Hz for ${howLong}s`);
trigger(gainNode.gain, howLong);
}
};
}
function trigger(parameter, howLong) {
parameter.setTargetAtTime(1, now(), smoothingInterval);
parameter.setTargetAtTime(0, now() + howLong, smoothingInterval);
}
<button id="playButton">play</button>
start()
上创建一个新的OscillatorNode
。 - Joaquín OsetTargetAtTime
进行ADSR塑形)(当然,如果您需要处理多个振荡器,则需要使用增益、压缩和限制器节点进行一些额外的工作,以便您不会炸毁任何人的扬声器)。 - Mike 'Pomax' Kamermans