我真的希望这个问题仍然是一个编程问题,而不是一个声音力学问题。。。给。。。
我正在做一些实验,以便弄清楚Web Audio API是如何工作的。我想做的是循环播放一个简单的"挂断电话"的声音。问题是,当声音结束时,你会听到一种很烦人的"咔嗒"声。我无法更好地解释它,但如果你测试代码,你可以听到它。
有什么办法可以避免这种情况吗?我可以应用一些过滤器或其他什么?
var audioContext = new (AudioContext || webkitAudioContext)();
var frequencyOffset = 0
function boop(){
// Our sound source is a simple triangle oscillator
var oscillator = audioContext.createOscillator(); // Create sound source
oscillator.type = 'triangle';
// Adding a gain node just to lower the volume a bit and to make the
// sound less ear-piercing
var gain = audioContext.createGain();
oscillator.connect(gain);
gain.connect(audioContext.destination);
gain.gain.value = 0.1;
// Just for fun let the frequency increase on each itteration
oscillator.frequency.value = 200 + frequencyOffset;
oscillator.start(0);
// The sound should last for 250ms
setTimeout(function(){
oscillator.disconnect();
oscillator.stop();
gain.disconnect();
}, 250);
frequencyOffset += 1;
}
setInterval(boop, 500);
这是音频问题,而不是编程问题。当波形在波的中间停止/切断时,而不是在过零点时,会发生您听到的咔嗒声。
音频模式中最好的简单解决方案是快速淡出,而不是停止播放。
稍微复杂一点的解决方案是找到下一个过零点,并在该点停止播放。
这里有一个关于我们为什么会听到点击声(这是人耳的事情)的简短解释,以及如何使用Web音频API绕过点击声的好例子:http://alemangui.github.io/blog//2015/12/26/ramp-to-value.html
这篇文章的主要收获是,消除点击的指数方法效果更好指数RampToValueAtTime和设置目标AtTime。
使用setTargetAtTime删除点击
var context = new AudioContext();
var oscillator = context.createOscillator();
var gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination)
oscillator.start();
stopButton.addEventListener('click', function() {
gainNode.gain.setTargetAtTime(0, context.currentTime, 0.015);
});
使用exponentialRampToValueAtTime删除点击
var context = new AudioContext();
var oscillator = context.createOscillator();
var gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination)
oscillator.start();
stopButton.addEventListener('click', function() {
// Important! Setting a scheduled parameter value
gainNode.gain.setValueAtTime(gainNode.gain.value, context.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.0001, context.currentTime + 0.03);
});
这两种方法在我的用例中都适用,exponentialRampToValueAtTime的效果稍好。使用setTargetAtTime,我仍然可以听到微弱的点击声。
看起来Web Audio API为开发人员提供了一种简单的方法,可以在不突然停止波形的情况下停止播放声源,并避免任何噪声和声音伪影。
- 创建你的声源(在我的例子中是一个振荡器)
- 创建一个增益节点并将其与声源连接
- 启动声源并将增益值设置为0。这样,即使从技术上讲是在播放,你也听不到声音
- 当您希望播放源时,将增益值设置为1,当不应该播放时,将其设置为0。增益节点将处理其余部分,不会听到任何点击声
var audioContext = new(AudioContext || webkitAudioContext)();
var frequencyOffset = 0
// Our sound source is a simple triangle oscillator
var oscillator = audioContext.createOscillator(); // Create sound source
oscillator.type = 'triangle';
// Adding a gain node just to lower the volume a bit and to make the
// sound less ear-piercing. It will also allow us to mute and replay
// our sound on demand
var gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
gainNode.gain.value = 0;
oscillator.frequency.value = 200;
oscillator.start(0);
function boop() {
gainNode.gain.value = 0.1;
// The sound should last for 250ms
setTimeout(function() {
gainNode.gain.value = 0;
}, 250);
oscillator.frequency.value++;
}
setInterval(boop, 500);
timmcliu的回答很好,但有两件事进一步降低了我的点击声:
- 我使用linearRampToValueAtTime。我想这会使变化更加平缓,从而软化点击
- 我已经将先决条件noop setValueAtTime也安排在未来。我想这会使更改从下一个渲染量子而不是当前量子开始应用。我认为如果没有这一点,渐变开始得太突然了,因为浏览器无法编辑已经预缓冲的渲染量(128个样本,IIUC)
基本上是这样的:
stopButton.addEventListener('click', function() {
let t = context.currentTime;
gainNode.gain.setValueAtTime(gainNode.gain.value, t + 0.02);
gainNode.gain.linearRampToValueAtTime(0.0001, t + 0.04);
});