使用网络音频控制音量 API

Volume control with Web Audio API

我正在做一个简单的项目来创建乐器,使用网络音频 API,并编写了以下代码片段(您可以按 'Q' 来演奏音符):

var audio = new AudioContext();

var volume = 0;
var attack = 1;
var release = 1;

var carrier = audio.createOscillator();
carrier.frequency.value = 440.00;
carrier.type = "sine";
carrier.start();

var carrier_volume = audio.createGain();
carrier_volume.gain.linearRampToValueAtTime(volume, 0);
carrier.connect(carrier_volume);
carrier_volume.connect(audio.destination);

document.addEventListener("keydown", function(e) {
  if(e.keyCode == 81) {
    carrier_volume.gain.linearRampToValueAtTime(1, audio.currentTime + attack);
  }
});

document.addEventListener("keyup", function(e) {
  if(e.keyCode == 81) {
    carrier_volume.gain.linearRampToValueAtTime(0, audio.currentTime + release);
  }
});

(如果您不熟悉术语:'attack' 是音符达到峰值所需的时间,在我的示例中为 1 秒,而 'release' 是所需的时间在有人释放按键后消失,在此示例中也是 1 秒)。

问题是 'clicking' 您可以在音符播放前后听到的声音。我做了一些研究:

http://modernweb.com/2014/03/31/creating-sound-with-the-web-audio-api-and-oscillators/

发现应该是切断声波造成的,所以我应该保持0分贝的音符播放,并根据需要提高/降低音量。但是,它仅在 Chrome 特定 并且仅在我直接设置音量时才 有效,如下所示: carrier_volume.gain.value = 1 .它不起作用,即使在 Chrome 中,如果我使用 linearRampToValueAtTime() 函数。

如果我尝试直接将初始音量设置为 0,会发生其他奇怪的事情。通过在初始化时使用carrier_volume.gain.value = 0,弹奏的第一个音符将被截断,但接下来的音符将正常弹奏。

有没有人找到解决这种烦人的咔嗒声的方法?同时使用 gain.valuelinearRampToValueAtTime() 时的延迟问题是什么?

所以,这就是交易 - linearRampToValueAtTime 需要一个开始时间。您打算 "now" - 当按下键时 - 但您需要通过在按下键时设置当前值来使其明确。此外,您不应在创建时使用 linearRamp - 只需直接设置值即可。

如果您直接将初始音量设置为 0(通过 .value),它不应该完全切断,但第一个斜坡不会有起点 - 因此它将保持为零,直到 linearRamp 的时间过去,然后它将跳转到 1.

试试这个:

var audio = new AudioContext();

var volume = 0;
var attack = 1;
var release = 1;

var carrier = audio.createOscillator();
carrier.frequency.value = 440.00;
carrier.type = "sine";
carrier.start();

var carrier_volume = audio.createGain();
carrier_volume.gain.linearRampToValueAtTime(volume, 0);
carrier.connect(carrier_volume);
carrier_volume.connect(audio.destination);

// remember whether we're playing or not; otherwise the keyboard repeat will confuse us
var playing = false;

document.addEventListener("keydown", function(e) {
  if((e.keyCode == 81) && !playing) {
    // first, in case we're overlapping with a release, cancel the release ramp
    carrier_volume.gain.cancelScheduledValues(audio.currentTime);

    // now, make sure to set a "scheduling checkpoint" of the current value
    carrier_volume.gain.setValueAtTime(carrier_volume.gain.value,audio.currentTime);

    // NOW, set the ramp
    carrier_volume.gain.linearRampToValueAtTime(1, audio.currentTime + attack);
    // Note that ideally, we would check the current value from above, and calculate 
    // the length of the attack based on it to keep a constant angle of the attack,
    // rather than a constant time.  (If we're half-way through a release when we 
    // start a new attack, the attack should only take 0.5s since we're already at 0.5.)

    playing = true;
  }
});

document.addEventListener("keyup", function(e) {
  if((e.keyCode == 81) && playing) {
    // first, in case we're overlapping with an attack, cancel the attack ramp
    carrier_volume.gain.cancelScheduledValues(audio.currentTime);

    // now, make sure to set a "scheduling checkpoint" of the current value
    carrier_volume.gain.setValueAtTime(carrier_volume.gain.value,audio.currentTime);

    // NOW, set the ramp
    carrier_volume.gain.linearRampToValueAtTime(0, audio.currentTime + release);

    // Note that ideally, we would check the current value from above, and calculate 
    // the length of the release based on it to keep a constant angle of the release,
    // rather than a constant time.  (If we're half-way through an attack when we 
    // start a new release, the release should only take 0.5s since we're already at 0.5.)
    playing = false;
  }
});

设置音量键

gainNode.gain.setValueAtTime(0.5, context.currentTime);

<button id="start">playSound</button>
const audioPlay = async url => {
  const context = new AudioContext();
  var gainNode = context.createGain();

  const source = context.createBufferSource();
  const audioBuffer = await fetch(url)
    .then(res => res.arrayBuffer())
    .then(ArrayBuffer => context.decodeAudioData(ArrayBuffer));

  source.buffer = audioBuffer;

  source.connect(gainNode);
  gainNode.connect(context.destination);
  gainNode.gain.setValueAtTime(0.5, context.currentTime);  // volume, 0 means mute
  source.start();
};

document.querySelector('#start').onclick = () => audioPlay('music/2.ogg');