网络音频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>

所以,阅读这些问题后,我发现有几件事正在发生:

因此,第一个 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 结束。因此仍然存在很小的故障风险。您可以通过在末尾添加另一个线性斜坡来避免这种情况。不过我觉得没必要。