Web Audio API 简介

Avatar of Greg Hovanesyan
Greg Hovanesyan 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

Web Audio API 允许我们在浏览器中直接生成声音。它使您的网站、应用程序和游戏更加有趣和引人入胜。您甚至可以构建特定于音乐的应用程序,例如鼓机和合成器。在本文中,我们将通过构建一些有趣且简单的项目来学习如何使用 Web Audio API。

入门

让我们先了解一些术语。Web Audio API 中的所有音频操作都在一个音频上下文中处理。每个基本的音频操作都由音频节点执行,这些节点连接在一起,形成一个音频路由图。在播放任何声音之前,您需要创建此音频上下文。这与我们如何使用<canvas>元素创建绘图上下文非常相似。以下是我们如何创建音频上下文

var context = new (window.AudioContext || window.webkitAudioContext)();

Safari 需要 webkit 前缀来支持 AudioContext,因此您应该使用该行而不是new AudioContext();

通常,Web Audio API 的工作流程如下所示

创建音频上下文 - 数据重新计算尺寸= 创建源 -> 连接滤波器节点 -> 连接到目标” />

有三种类型的源

  1. 振荡器 – 数学计算的声音
  2. 音频样本 – 来自音频/视频文件
  3. 音频流 – 来自网络摄像头或麦克风的音频

让我们从振荡器开始

振荡器是一种重复的波形。它具有频率和峰值振幅。除了频率和振幅之外,振荡器的最重要的特性之一是其波形的形状。四个最常用的振荡器波形是正弦波、三角波、方波和锯齿波。

也可以创建自定义形状。不同的形状适用于不同的合成技术,它们会产生不同的声音,从平滑到刺耳。

Web Audio API 使用OscillatorNode来表示重复的波形。我们可以使用上面显示的所有波形形状。为此,我们必须像这样分配value属性

OscillatorNode.type = 'sine'|'square'|'triangle'|'sawtooth';

您也可以创建一个自定义波形。您使用setPeriodicWave()方法创建波形的形状,这将自动将类型设置为custom。让我们听听不同的波形如何产生不同的声音

查看示例。

自定义波形是使用傅里叶变换创建的。如果您想了解有关自定义波形形状的更多信息(例如如何制作警笛),您可以从这里获得很好的资源。

运行振荡器

让我们尝试发出一些声音。为此,我们需要以下步骤

  1. 我们必须创建一个 Web Audio API 上下文
  2. 在该上下文中创建振荡器节点
  3. 选择波形类型
  4. 设置频率
  5. 将振荡器连接到目标
  6. 启动振荡器

让我们将这些步骤转换为代码。

var context = new (window.AudioContext || window.webkitAudioContext)();

var oscillator = context.createOscillator();

oscillator.type = 'sine';
oscillator.frequency.value = 440;
oscillator.connect(context.destination);
oscillator.start();

请注意我们如何定义音频上下文。Safari 需要webkit前缀,因此我们使其跨浏览器兼容。

然后我们创建振荡器并设置波形的类型。类型的默认值为sine,因此您可以跳过此行,我只是喜欢添加它以使其更清晰且易于更新。我们将频率值设置为 440,这是 A4 音符(这也是默认值)。音乐音符 C0 到 B8 的频率范围为 16.35 到 7902.13Hz。我们将在本文后面查看一个播放许多不同音符的示例。

现在我们知道了所有这些,让我们也使音量可调。为此,我们需要在上下文中创建增益节点,将其连接到链中,并将增益连接到目标。

var gain = context.createGain();
oscillator.connect(gain);
gain.connect(context.destination);

var now = context.currentTime;
gain.gain.setValueAtTime(1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.5);
oscillator.start(now);
oscillator.stop(now + 0.5);

现在您已经了解了如何使用振荡器,这是一个很好的练习。此示例 设置了振荡器代码。尝试创建一个简单的应用程序,当您上下移动屏幕光标时更改音量,当您左右移动光标时更改频率。

Web Audio API 的时间控制

构建音频软件时最重要的事情之一是管理时间。对于这里需要的精度,使用 JavaScript 时钟不是最佳实践,因为它根本不够精确。但是 Web Audio API 带有currentTime属性,这是一个不断增加的双精度硬件时间戳,可用于安排音频播放。它在声明音频上下文时从 0 开始。尝试运行console.log(context.currentTime)以查看时间戳。

例如,如果您希望振荡器立即播放,则应运行oscillator.start(0)(您可以省略 0,因为它是默认值)。但是,您可能希望它从现在开始的一秒钟后开始播放,播放两秒钟,然后停止。以下是操作方法

var now = context.currentTime;
oscillator.play(now + 1);
oscillator.stop(now + 3);

这里有两个方法需要提及。

AudioParam.setValueAtTime(value, startTime)方法在精确的时间安排值的更改。例如,您希望在一秒钟内更改振荡器的频率值

oscillator.frequency.setValueAtTime(261.6, context.currentTime + 1);

但是,当您想要立即更新值时,您也可以使用它,例如.setValueAtTime(value, context.currentTime)。您可以通过修改AudioParam的value属性来设置值,但是如果在自动化事件(使用AudioParam方法安排的事件)发生的同时更新值,则任何对值的更新都会被忽略,而不会引发异常。

AudioParam.exponentialRampToValueAtTime(value, endTime)方法安排值的逐渐变化。此代码将在 1 秒内以指数方式降低振荡器的音量,这是一种平滑停止声音的好方法

gain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 1);

我们不能使用 0 作为值,因为值需要为正,所以我们改为使用一个非常小的值。

创建声音类

停止振荡器后,您无法再次启动它。您没有做错任何事,这是 Web Audio API 的一项优化性能的功能。我们可以做的是创建一个声音类,它将负责创建振荡器节点,以及播放和停止声音。这样,我们就可以多次调用声音。我将为此使用 ES6 语法

class Sound {

  constructor(context) {
    this.context = context;
  }

  init() {
    this.oscillator = this.context.createOscillator();
    this.gainNode = this.context.createGain();

    this.oscillator.connect(this.gainNode);
    this.gainNode.connect(this.context.destination);
    this.oscillator.type = 'sine';
  }

  play(value, time) {
    this.init();

    this.oscillator.frequency.value = value;
    this.gainNode.gain.setValueAtTime(1, this.context.currentTime);
            
    this.oscillator.start(time);
    this.stop(time);

  }

  stop(time) {
    this.gainNode.gain.exponentialRampToValueAtTime(0.001, time + 1);
    this.oscillator.stop(time + 1);
  }

}

我们将上下文传递给构造函数,以便我们可以在同一个上下文中创建Sound类的所有实例。然后我们有init方法,它创建振荡器和所有必要的滤波器节点,连接它们等。Play方法接受值(它将播放的音符的频率,以赫兹为单位)和它应播放的时间。但首先,它创建振荡器,并且每次我们调用play方法时都会发生这种情况。stop方法在一秒钟内以指数方式降低音量,直到完全停止振荡器。因此,每当我们需要再次播放声音时,我们都会创建一个新的sound类实例并调用play方法。现在我们可以播放一些音符了

let context = new (window.AudioContext || window.webkitAudioContext)();
let note = new Sound(context);
let now = context.currentTime;
note.play(261.63, now);
note.play(293.66, now + 0.5);
note.play(329.63, now + 1);
note.play(349.23, now + 1.5);
note.play(392.00, now + 2);
note.play(440.00, now + 2.5);
note.play(493.88, now + 3);
note.play(523.25, now + 3.5);

这将在同一个上下文中播放 C D E F G A B C。如果您想知道音符的频率(以赫兹为单位),可以在这里找到它们。

了解了所有这些,我们就可以构建像木琴一样的东西!它创建一个新的Sound实例并在mouseenter时播放它。您可以查看示例并尝试自己做一个作为练习。

查看示例 播放木琴(Web Audio API),由 Greg Hovanesyan (@gregh) 在 CodePen 上发布。

我创建了一个游乐场,其中包含所有必需的 HTML 和 CSS,以及我们创建的Sound类。使用data-frequency属性获取音符值。在此尝试

使用录制的声音

现在你已经用振荡器构建了一些东西,让我们看看如何处理录制的声音。有些声音很难用振荡器来再现。在很多情况下,为了使用真实的声音,你必须使用录制的声音。这可以是`.mp3`、`.ogg`、`.wav`等。查看完整列表以获取更多信息。我喜欢使用`.mp3`,因为它轻量级、支持广泛,并且具有相当好的音质。

你不能像处理图片那样简单地通过URL获取声音。我们必须运行一个XMLHttpRequest来获取文件,解码数据,并将数据放入缓冲区。

class Buffer {

  constructor(context, urls) {  
    this.context = context;
    this.urls = urls;
    this.buffer = [];
  }

  loadSound(url, index) {
    let request = new XMLHttpRequest();
    request.open('get', url, true);
    request.responseType = 'arraybuffer';
    let thisBuffer = this;
    request.onload = function() {
      thisBuffer.context.decodeAudioData(request.response, function(buffer) {
        thisBuffer.buffer[index] = buffer;
        updateProgress(thisBuffer.urls.length);
        if(index == thisBuffer.urls.length-1) {
          thisBuffer.loaded();
        }       
      });
    };
    request.send();
  };

  loadAll() {
    this.urls.forEach((url, index) => {
      this.loadSound(url, index);
    })
  }

  loaded() {
    // what happens when all the files are loaded
  }

  getSoundByIndex(index) {
    return this.buffer[index];
  }

}

让我们看看构造函数。我们像在Sound类中一样在那里接收我们的上下文,接收将加载的URL列表,以及缓冲区的空数组。

然后我们有两个方法:loadSoundloadAllloadAll循环遍历URL列表并调用loadSound方法。传递索引非常重要,这样无论哪个请求先加载,我们都将缓冲的声音放入数组的正确元素中。这也让我们可以看到哪个请求是最后一个,这意味着在其完成后缓冲区已加载。

然后你可以调用loaded()方法,它可以执行一些操作,例如隐藏加载指示器。最后,getSoundByIndex(index)方法通过索引从缓冲区获取声音以进行播放。

decodeAudioData方法有一个更新的基于Promise的语法,但它在Safari中尚不可用。

context.decodeAudioData(audioData).then(function(decodedData) {
  // use the decoded data here
});

然后我们必须为声音创建类。现在我们有了用于处理录制声音的完整类。

class Sound() {

  constructor(context, buffer) {
    this.context = context;
    this.buffer = buffer;
  }

  init() {
    this.gainNode = this.context.createGain();
    this.source = this.context.createBufferSource();
    this.source.buffer = this.buffer;
    this.source.connect(this.gainNode);
    this.gainNode.connect(this.context.destination);
  }

  play() {
    this.setup();
    this.source.start(this.context.currentTime);
  }  

  stop() {
    this.gainNode.gain.exponentialRampToValueAtTime(0.001, this.context.currentTime + 0.5);
    this.source.stop(this.context.currentTime + 0.5);
  }

}

构造函数接受上下文和缓冲区。我们通过调用createBufferSource()方法来创建,而不是像以前那样调用createOscillator。缓冲区是我们使用getSoundByIndex()方法获取的音符(缓冲区数组中的元素)。现在,我们不是创建振荡器,而是创建缓冲区源,设置缓冲区,然后将其连接到目标(或增益和其他滤波器)。

let buffer = new Buffer(context, sounds);
buffer.loadAll();

sound = new Sound(context, buffer.getSoundByIndex(id));
sound.play();

现在我们必须创建一个缓冲区实例并调用loadAll方法,以将所有声音加载到缓冲区中。我们还有getSoundById方法来获取我们需要的精确声音,因此我们将声音传递给Sound并调用play()id可以存储为单击以播放声音的按钮上的数据属性。

这是一个使用所有这些内容的项目:缓冲区、录制的音符等。

查看Greg Hovanesyan在CodePen上的笔The Bluesman – You Can Play The Blues (Web Audio API) (@gregh)。

你可以使用该示例作为参考,但为了你自己的练习,我创建了一个游乐场。它包含所有必要的HTML和CSS,以及我在真实在电吉他上录制的音符的URL。尝试编写你自己的代码!

滤波器简介

Web Audio API允许你在声源和目标之间添加不同的滤波器节点。BiquadFilterNode是一个简单的低阶滤波器,它让你可以控制哪些频率部分应该被强调,哪些部分应该被衰减。这使你能够构建均衡器应用程序和其他效果。有8种类型的双二次滤波器:高通、低通、带通、低架、高架、峰值、陷波和全通。

高通滤波器可以很好地通过高频,但会衰减信号的低频分量。低通滤波器通过低频,但会衰减高频。它们也称为“低切”和“高切”滤波器,因为这解释了信号发生了什么。

高架低架滤波器用于控制声音的低音和高音。它们用于强调或降低给定频率以上或以下的信号。

你会在BiquadFilterNode接口中找到一个Q属性,它是一个双精度数,表示Q因子。品质因数或Q因子控制带宽,即受影响的频率数量。Q因子越低,带宽越宽,这意味着受影响的频率越多。Q因子越高,带宽越窄。

你可以在此处找到有关滤波器的更多信息,但我们已经可以构建参数均衡器了。它是一个均衡器,可以完全控制调整频率、带宽和增益。

让我们构建一个参数均衡器。

查看示例。

让我们看看如何将失真应用于声音。如果你想知道是什么让电吉他听起来像电吉他,那就是失真效果。我们使用WaveShaperNode接口来表示非线性失真器。我们需要做的是创建一个曲线来塑造信号,使其失真并产生特有的声音。我们不必花太多时间来创建曲线,因为它已经为我们做好了。我们也可以调整失真量。

查看示例。

结语

现在你已经了解了如何使用Web Audio API,我建议你自己尝试一下并创建自己的项目!

以下是一些用于处理Web音频的库

  • Pizzicato.js – Pizzicato旨在简化你通过Web Audio API创建和操作声音的方式。
  • webaudiox.js – webaudiox.js是一组辅助工具,将使使用Web Audio API变得更容易。
  • howler.js – 现代网络的Javascript音频库。
  • WAD – 使用HTML5 Web Audio API进行动态声音合成。它就像你耳朵的jQuery。
  • Tone.js – 一个Web Audio框架,用于在浏览器中制作交互式音乐。