不易~

Web Audio API简介

原文链接: css-tricks.com

Web Audio API让我们在浏览器中制造一些还不错的声音。这使你的站点,应用,游戏变得更加有趣吸引人。你还可以制造一些像鼓机或者合成器这样特别的音乐应用。在本文中,我们将学习通过Web Audio API做一些简单有趣的项目。

入门

让我们了解一些专业术语。在Web Audio API中,所有的音频操作都是在一个audio上下文中。每个基础的声音操作通过使用链接在一起的音频节点所展现的,从而形成音频路由图。在播放任何声音之前,你需要创建这个音频的上下文。这非常类似于创建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()方法来创建一个波形。可以自动的设置一个定制类型。我们来听听不同的波形产生的不同的声音:

看看这个CodePen例子

通常的波形运用傅里叶变换自定义波形。如果你想要学习更多自定义的波形(比如,怎样制作警笛)。你可以学习这个优秀的资源中学习。

运行振荡器

我们来一起试试制造一些噪音。这是我们所需要做的:

  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前缀。所以我们得让它跨浏览器兼容。

然后我们创建一个振荡器并设置一个波形。默认值是正弦型,所以你可以跳过这一行,我只是想添加它使其更加清晰,更加容易更新。我们可以将频率值设置440HZ,即A4音符(也是默认值)。这些音符从C0到B8的频率都是在16.35到7902.13HZ的范围之间。在这篇文章中,我们将看一个播放一些不同的音符的例子

现在,当我们知道所有这些之后,我们也可以调节音量。为此,我们需要在上下文中创建一个gain节点,将其连在链中,还有连接gain节点连接到目标。

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);

现在你对使用振荡器有一些了解,这是一个很好的练习。这个Codepen链接有振荡器的设置代码。尝试来做一个简单的应用程序,在屏幕上下移动光标来改变音量,左右移动来改变频率。

Web Audio API的计时

创建一个音频软件很重要的事情之一是管理时间。对于这里需要的精度,使用一个JavaScript时钟不是最佳的实践。因为它不够精确。然而,Web Audio API附带了一个currentTime属性,这是需要不断增加的双硬件时间戳,可用于调度音频播放。当音频上下文建立之后,它是从0开始的。尝试运行一下console.log(context.currentTime)查看时间戳。

例如,如果你想要立马播放振荡器,应该立马运行oscillator.start(0)(你可以省略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。但是,如果它们与自动化事件(使用AudioParam方法的事件)同时发生,则任何的值被忽略而不抛出异常。

AudioParam.exponentialRampToValueAtTime(value, endTime) 方法用来持续的值的变化。这段代码将在一秒钟之内以指数形式减少振荡器的音量。这是一个平稳停止声音的好办法。

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

我们不能使用0作为值,因为需要一个正值,所以我们可以用一个很小的值进行代替。

创建一个Sound类

一旦停止一个振荡器,就无法重新启动它。你没有做错任何事情,这是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实例并播放。你可以查看例子并尝试自己制作一个练习。

看看由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类中接收上下文。接收到将要加载到的URLa列表,以及缓冲区的空数组。

我们有两种方法:loadSound和loadAll。loadAll循环遍历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()方法获得音符(缓冲区数组的元素)。现在我们不是使用振荡器创建一个缓冲源,而是设置缓冲区,然后连接目标(或者gain节点还有其它滤波器)。

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

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

现在,我们必须创建一个缓冲区实例并调用loadAll方法,加载所有声音到缓冲区中去。我们还有getSoundById方法来获取需要的声音得方法,因此我们将声音给Sound类和调用play()方法。该id可以存储在你可以播放声音的按钮属性中。

这有一个使用缓冲区,录音,音符等的项目:

看看这个Pen The Bluesman - 你可以演奏蓝调 (Web Audio API) 是Greg Hovanesyan (@gregh)在 CodePen上制作的。

你可以使用这个例子作为参考,但是除了你自己的练习,这里还有一个我创建的小广场。这里有所有必要的HTML,CSS,还有一些我在真实的电吉上录的音符的URL链接。试试编写自己的代码吧!

介绍过滤器

Web Audio API允许你添加不同的筛选器节点在声源和目标之间。BiquadFilterNode是一个简单的低阶滤波器,可以控制频率这一部分,哪部分需要加强还是减弱。这让你建立一个均衡器应用和其他效果。这里有八种的双二阶滤波器:高通,低通,带通,低架,高架,高峰,缺口和全通。

高通是一个也可以传递更高频率的过滤器,但是会衰减信号的低频分量。低通通过较低频率,但衰减较高频率。它们也被称为“低切”和“高切”滤波器,因为它解释了信号发生了什么。

高架低架 是用于控制声音的低音和高音的滤波器,它们用于强调或减少高于或低于给定频率的信号。

你将找到Q属性的BiquadFilterNode接口,这代表 Q Factor的双重接口。品质因数或Q Factor控制带宽,影响频率高低。Q Factor越低,带宽越宽,意味着影响的频率越多。Q Factor越高,带宽越窄。

你可以找到更多的滤波器的信息,这里。但我们已经可以构建参数均衡器。它是一个均衡器,可以完全控制频率,带宽和gain节点。

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

看看这个pen例子

我们来看看怎样运用在声音的失真上。如果你想要的制作一个听起来像电吉他的声音,这就是失真效应。我们使用 WaveShaperNode 接口来代表一个非线性失真器。我们需要做的是创建一条曲线来代表信号,扭曲并产生特定的声音。我们不必花一些时间创建曲线,我们已经做好了。我们也可以调整失真量:

看看这个CodePen例子

后记

现在你已经了解如何建立Web Audio API,我建议你试试它制作一些自己的项目吧!

这里有一些处理Web音频的库:

  • Pizzicato.js - Pizzicato旨在通过Web Audio API简化您创建和操作声音的方式

  • webaudiox.js - webaudiox.js是一个助手,将让你轻松的使用WebAudio API

  • howler.js - 现代网络的Javascript音频库

  • WAD - 使用HTML5的Web Audio API进行动态合成声音。这很像为了你耳朵的jQuery

  • Tone.js - 用于在浏览器中制作交互式音乐的Web Audio框架