网络音频api"clicks""crackles""pops"&失真噪声消除。我还能做更多吗?
Web audio api "clicks" "crackles" "pops" & distortion noise elimination. Can I do any more?
我最近开始研究网络音频 api,以便将声音和音乐引入基于 canvas 的游戏中。不久之后我注意到同时播放声音(例如 rapid 重复射击声音会在几秒钟内衰减,或者,在背景音乐 rapid 上播放射击声音会导致可怕的失真.
为了弄清造成不想要的噪音的原因,我制作了一个非常简单的键盘乐器来演奏音符。当播放单个音符时,您会听到声音结束时发出难听的咔哒声。当连续按下 rapid 个按键时,情况变得更糟。
// create the context
const actx = new AudioContext();
function playNoteUgh(freq = 261.63, type = "sine", decay = 0.5) {
// create oscillator and gain nodes
let osc = actx.createOscillator();
let vol = actx.createGain();
// set the supplied values
osc.frequency.value = freq;
osc.type = type;
vol.gain.value = 0.1;
//create the audio graph
osc.connect(vol).connect(actx.destination);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay);
}
function playNote(freq = 261.63, type = "sine", decay = 2) {
// Create a new oscillator and audio graph for each keypress
createOsc(freq, type, decay);
}
function createOsc(freq, type, decay) {
console.log(freq, type, decay);
// create oscillator, gain and compressor nodes
let osc = actx.createOscillator();
let vol = actx.createGain();
let compressor = actx.createDynamicsCompressor();
// set the supplied values
osc.frequency.value = freq;
osc.type = type;
// set the volume value so that we do not overload the destination
// when multiple voices are played simmultaneously
vol.gain.value = 0.1;
//create the audio graph
osc.connect(vol).connect(compressor).connect(actx.destination);
// ramp up to volume so that we minimise the
// ugly "click" when the key is pressed
vol.gain.exponentialRampToValueAtTime(
vol.gain.value,
actx.currentTime + 0.03
);
// ramp down to minimise the ugly click when the oscillator stops
vol.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + decay);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay + 0.03);
}
window.addEventListener("keydown", keyDown, { passive: false });
// Some musical note values:
let C4 = 261.63,
D4 = 293.66,
E4 = 329.63,
F4 = 349.23,
G4 = 392,
A5 = 440,
B5 = 493.88,
C5 = 523.25,
D5 = 587.33,
E5 = 659.25;
function keyDown(event) {
let key = event.key;
if (key === "q") playNoteUgh(C4);
if (key === "w") playNoteUgh(D4);
if (key === "e") playNoteUgh(E4);
if (key === "r") playNoteUgh(F4);
if (key === "t") playNoteUgh(G4);
if (key === "y") playNoteUgh(A5);
if (key === "u") playNoteUgh(B5);
if (key === "i") playNoteUgh(C5);
if (key === "o") playNoteUgh(D5);
if (key === "p") playNoteUgh(E5);
}
<p>Keys Q through P play C4 through E4</p>
所以,阅读这些问题后,我发现有几件事正在发生:
当音频音量 <-1 或 >1 时重载目标
突然打开和关闭振荡器会引起咔嗒声 Web
音频:丑陋的咔嗒声和人耳
因此,第一个 link 建议我们通过增益节点控制音量,并通过动态压缩器路由音乐,而不是直接 linking 到 AudioContext 目的地。我还读到将增益值降低十倍
为了减少 'Ugly clicks',建议我们上下调整振荡器,而不是突然启动和停止它们。
此 post How feasible is it to use the Oscillator.connect() and Oscillator.disconnect() methods to turn on/off sounds in an app built with the Web Audio API? 中的想法建议您可以在需要时即时创建振荡器。
根据以上信息,我想到了这个。
// create the context
const actx = new AudioContext();
function playNote(freq = 261.63, type = "sine", decay = 2) {
// Create a new oscillator and audio graph for each keypress
createOsc(freq, type, decay);
}
function createOsc(freq, type, decay) {
// create oscillator, gain and compressor nodes
let osc = actx.createOscillator();
let vol = actx.createGain();
let compressor = actx.createDynamicsCompressor();
// set the supplied values
osc.frequency.value = freq;
osc.type = type;
// set the volume value so that we do not overload the destination
// when multiple voices are played simmultaneously
vol.gain.value = 0.1;
//create the audio graph
osc.connect(vol).connect(compressor).connect(actx.destination);
// ramp up to volume so that we minimise the
// ugly "click" when the key is pressed
vol.gain.exponentialRampToValueAtTime(
vol.gain.value,
actx.currentTime + 0.03
);
// ramp down to minimise the ugly click when the oscillator stops
vol.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + decay);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay + 0.03);
}
window.addEventListener("keydown", keyDown, { passive: false });
// Some musical note values:
let C4 = 261.63,
D4 = 293.66,
E4 = 329.63,
F4 = 349.23,
G4 = 392,
A5 = 440,
B5 = 493.88,
C5 = 523.25,
D5 = 587.33,
E5 = 659.25;
function keyDown(event) {
let key = event.key;
if (key === "1") playNote(C4);
if (key === "2") playNote(D4);
if (key === "3") playNote(E4);
if (key === "4") playNote(F4);
if (key === "5") playNote(G4);
if (key === "6") playNote(A5);
if (key === "7") playNote(B5);
if (key === "8") playNote(C5);
if (key === "9") playNote(D5);
if (key === "0") playNote(E5);
}
<p>Key 1 to 0 play C4 through to E5</p>
我现在的问题是,我这样做是否正确,我是否可以做更多,因为点击和失真已显着减少,但如果我在键盘上有点疯狂,仍然可以检测到!
我非常感谢并对此提出反馈,所以,提前致谢。
当前形式的音量自动化应该没有任何影响。
你先设置音量。
vol.gain.value = 0.1;
然后您定义一个斜坡,它应该斜坡到相同的值。
vol.gain.exponentialRampToValueAtTime(
vol.gain.value,
actx.currentTime + 0.03
);
结果是0.1
的常量值。
假设您想要最初设置音量,并希望保持音量不变,直到 actx.currentTime + decay
,然后它最终会再次淡出 0.03
秒。其代码如下所示。
const currentTime = actx.currentTime;
// This defines the initial value.
vol.gain.setValueAtTime(0.1, currentTime);
// This sets the starting point for the ramp.
vol.gain.setValueAtTime(0.1, currentTime + decay);
// Finally this schedules the fade out.
vol.gain.exponentialRampToValueAtTime(
0.001,
currentTime + decay + 0.03
);
指数斜坡不能以 0 结束。因此仍然存在很小的故障风险。您可以通过在末尾添加另一个线性斜坡来避免这种情况。不过我觉得没必要。
我最近开始研究网络音频 api,以便将声音和音乐引入基于 canvas 的游戏中。不久之后我注意到同时播放声音(例如 rapid 重复射击声音会在几秒钟内衰减,或者,在背景音乐 rapid 上播放射击声音会导致可怕的失真.
为了弄清造成不想要的噪音的原因,我制作了一个非常简单的键盘乐器来演奏音符。当播放单个音符时,您会听到声音结束时发出难听的咔哒声。当连续按下 rapid 个按键时,情况变得更糟。
// create the context
const actx = new AudioContext();
function playNoteUgh(freq = 261.63, type = "sine", decay = 0.5) {
// create oscillator and gain nodes
let osc = actx.createOscillator();
let vol = actx.createGain();
// set the supplied values
osc.frequency.value = freq;
osc.type = type;
vol.gain.value = 0.1;
//create the audio graph
osc.connect(vol).connect(actx.destination);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay);
}
function playNote(freq = 261.63, type = "sine", decay = 2) {
// Create a new oscillator and audio graph for each keypress
createOsc(freq, type, decay);
}
function createOsc(freq, type, decay) {
console.log(freq, type, decay);
// create oscillator, gain and compressor nodes
let osc = actx.createOscillator();
let vol = actx.createGain();
let compressor = actx.createDynamicsCompressor();
// set the supplied values
osc.frequency.value = freq;
osc.type = type;
// set the volume value so that we do not overload the destination
// when multiple voices are played simmultaneously
vol.gain.value = 0.1;
//create the audio graph
osc.connect(vol).connect(compressor).connect(actx.destination);
// ramp up to volume so that we minimise the
// ugly "click" when the key is pressed
vol.gain.exponentialRampToValueAtTime(
vol.gain.value,
actx.currentTime + 0.03
);
// ramp down to minimise the ugly click when the oscillator stops
vol.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + decay);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay + 0.03);
}
window.addEventListener("keydown", keyDown, { passive: false });
// Some musical note values:
let C4 = 261.63,
D4 = 293.66,
E4 = 329.63,
F4 = 349.23,
G4 = 392,
A5 = 440,
B5 = 493.88,
C5 = 523.25,
D5 = 587.33,
E5 = 659.25;
function keyDown(event) {
let key = event.key;
if (key === "q") playNoteUgh(C4);
if (key === "w") playNoteUgh(D4);
if (key === "e") playNoteUgh(E4);
if (key === "r") playNoteUgh(F4);
if (key === "t") playNoteUgh(G4);
if (key === "y") playNoteUgh(A5);
if (key === "u") playNoteUgh(B5);
if (key === "i") playNoteUgh(C5);
if (key === "o") playNoteUgh(D5);
if (key === "p") playNoteUgh(E5);
}
<p>Keys Q through P play C4 through E4</p>
所以,阅读这些问题后,我发现有几件事正在发生:
当音频音量 <-1 或 >1 时重载目标
突然打开和关闭振荡器会引起咔嗒声 Web 音频:丑陋的咔嗒声和人耳
因此,第一个 link 建议我们通过增益节点控制音量,并通过动态压缩器路由音乐,而不是直接 linking 到 AudioContext 目的地。我还读到将增益值降低十倍
为了减少 'Ugly clicks',建议我们上下调整振荡器,而不是突然启动和停止它们。
此 post How feasible is it to use the Oscillator.connect() and Oscillator.disconnect() methods to turn on/off sounds in an app built with the Web Audio API? 中的想法建议您可以在需要时即时创建振荡器。
根据以上信息,我想到了这个。
// create the context
const actx = new AudioContext();
function playNote(freq = 261.63, type = "sine", decay = 2) {
// Create a new oscillator and audio graph for each keypress
createOsc(freq, type, decay);
}
function createOsc(freq, type, decay) {
// create oscillator, gain and compressor nodes
let osc = actx.createOscillator();
let vol = actx.createGain();
let compressor = actx.createDynamicsCompressor();
// set the supplied values
osc.frequency.value = freq;
osc.type = type;
// set the volume value so that we do not overload the destination
// when multiple voices are played simmultaneously
vol.gain.value = 0.1;
//create the audio graph
osc.connect(vol).connect(compressor).connect(actx.destination);
// ramp up to volume so that we minimise the
// ugly "click" when the key is pressed
vol.gain.exponentialRampToValueAtTime(
vol.gain.value,
actx.currentTime + 0.03
);
// ramp down to minimise the ugly click when the oscillator stops
vol.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + decay);
osc.start(actx.currentTime);
osc.stop(actx.currentTime + decay + 0.03);
}
window.addEventListener("keydown", keyDown, { passive: false });
// Some musical note values:
let C4 = 261.63,
D4 = 293.66,
E4 = 329.63,
F4 = 349.23,
G4 = 392,
A5 = 440,
B5 = 493.88,
C5 = 523.25,
D5 = 587.33,
E5 = 659.25;
function keyDown(event) {
let key = event.key;
if (key === "1") playNote(C4);
if (key === "2") playNote(D4);
if (key === "3") playNote(E4);
if (key === "4") playNote(F4);
if (key === "5") playNote(G4);
if (key === "6") playNote(A5);
if (key === "7") playNote(B5);
if (key === "8") playNote(C5);
if (key === "9") playNote(D5);
if (key === "0") playNote(E5);
}
<p>Key 1 to 0 play C4 through to E5</p>
我现在的问题是,我这样做是否正确,我是否可以做更多,因为点击和失真已显着减少,但如果我在键盘上有点疯狂,仍然可以检测到!
我非常感谢并对此提出反馈,所以,提前致谢。
当前形式的音量自动化应该没有任何影响。
你先设置音量。
vol.gain.value = 0.1;
然后您定义一个斜坡,它应该斜坡到相同的值。
vol.gain.exponentialRampToValueAtTime(
vol.gain.value,
actx.currentTime + 0.03
);
结果是0.1
的常量值。
假设您想要最初设置音量,并希望保持音量不变,直到 actx.currentTime + decay
,然后它最终会再次淡出 0.03
秒。其代码如下所示。
const currentTime = actx.currentTime;
// This defines the initial value.
vol.gain.setValueAtTime(0.1, currentTime);
// This sets the starting point for the ramp.
vol.gain.setValueAtTime(0.1, currentTime + decay);
// Finally this schedules the fade out.
vol.gain.exponentialRampToValueAtTime(
0.001,
currentTime + decay + 0.03
);
指数斜坡不能以 0 结束。因此仍然存在很小的故障风险。您可以通过在末尾添加另一个线性斜坡来避免这种情况。不过我觉得没必要。