【问题标题】:Web audio api "clicks" "crackles" "pops" & distortion noise elimination. Can I do any more?网络音频 api“点击”“噼啪声”“爆裂声”和失真噪声消除。我还能做些什么吗?
【发布时间】:2022-03-14 21:20:52
【问题描述】:

我最近开始研究网络音频 api,以便将声音和音乐引入基于画布的游戏中。不久之后,我注意到同时播放声音(例如,快速重复的射击声音会在几秒钟内衰减,或者在背景音乐中快速播放的射击声音会导致可怕的失真。

为了弄清楚产生不需要的噪音的原因,我制作了一个非常简单的键盘乐器来演奏音符。播放单个音符时,您会在声音结束时听到难看的咔嗒声。当多个键快速连续按下时,情况会变得更糟。

// 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>

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

因此,第一个链接建议我们通过增益节点控制音量,并通过动态压缩器路由音乐,而不是直接链接到 AudioContext 目标。我也读过将增益值降低十倍

为了减少“难看的咔哒声”,建议我们上下调整振荡器,而不是突然启动和停止它们。

本文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>

我现在的问题是,我这样做是否正确,我是否可以做得更多,因为咔哒声和失真已显着减少,但如果我在键盘上有点疯狂,仍然可以检测到!

非常感谢并就此提供反馈,因此,提前致谢。

【问题讨论】:

    标签: javascript web-audio-api game-development


    【解决方案1】:

    当前形式的音量自动化不应该有任何影响。

    你先设置音量。

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

    【讨论】:

    • 这与我所拥有的略有不同,但我理解其中的区别。我试图提高音量(以最小化振荡器的启动声音),但当然我正在提高到与从 doh 开始的相同值!有趣的是,您指出了故障呈指数级下降的风险,因为这已经给我带来了问题!
    • 但是,如果我按照您的建议添加线来设置​​斜坡下降的起点,我丑陋的振荡器终止点击奇怪地回来了?
    • 实际上,它有两条 setValueAtTime 行导致点击:// 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); 如果增益值已经为 0.1,我可以省略第一条 setValue 行,然后第二条没有发出并从指定的时间开始下降。这可能很有用,谢谢。
    • 抱歉,我以为您正在尝试淡出以移除点击。通常,振荡器开始时应该没有点击,因为它总是从 0 开始。很高兴知道您找到了解决方案。
    • 我一直在考虑你的线性斜坡到零的想法。如果我不这样做,即使听不见,振荡器也会继续运行,并且 不会 被垃圾收集器删除,但是,如果我们最后减速到零,振荡器会被收集吗?这让我进一步思考,有没有办法在任何给定的上下文中拉出正在运行的振荡器列表?
    猜你喜欢
    • 1970-01-01
    • 2019-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-25
    相关资源
    最近更新 更多