大话 Web-Audio-Api

3,250 阅读10分钟

Logic proX


在html5 <audio> 元素出现之前,一个网页想要发声,就得需要flash或者其他插件的辅助。而<audio>标签的出现,使网页在游戏(的音效)和网页交互(音频)方面有了极大的飞跃。

Web-Audio-API 是一个用与WebApp 统筹以及合成声音的高级Api。
这套Api的目标是在音频处理方面能够达到当今很多游戏音频引擎,混音器,处理器,滤波器的水平。 以下就是相关介绍。

首先从 AudioContext 说起

AudioContext 是用来控制管理所有声音用的(网页上的声音吧)。
在实际用途中,我们可以用AudioContext的实例,创建一个音频源,或者很多音频源并且将他们连接到音频出口(一般是扬声器)。这种连接并不一定是直接连接了,这里可以在中间连接上一系列的AudioNodes(你可以当做 中间处理器吧 就跟美图里面的滤镜一样 最后还是要导出到相册),这些AudioNodes的作用就是对声音信号做相应的处理。

CAUTION! 后面简称Web-Audio-API => WAA

一个简单的AudioContext实例可以支持很多声音,还有非常复杂的声音图像,所以我们只需要简单的创建一个实例就可以展现WebAudioAPI 强大的本领。许多有趣的WAA函数比如创建一个AudioNodes 以及解码一个音频文件 都是属于AudioContext的。

如下 创建一个AudioContext的代码片段

var context;
window.addEventLisener('load',init,false)

function init(){
    try {
        window.AudioContext = window.AudioContext||window.webkitAudioContext;
        context = new AudioContext();
    }
    catch(e){

        alert('MLGB 你的浏览器连个Web-Audio-API 都不支持!!')
    }


}

可以看到在这里webkit 又要加一个前缀,不知道是因为他是先行者,还是要特立独行一把。

加载声音

Web-Audio-API 使用AudioBuffer 来加载短 或者中等长度的音频。最基本的加载方式就是使用XMLHttpRequest来获取声音文件。

Web-Audio-API 也支持很多种格式的音频,比如WAV, MP3, AAC, OGG 还有其他很多啦,具体看这里支持的音频格式

以下代码是展示如何用XMLHttpRequest来加载音频

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

function loadDogSound(url){
    var request = new XMLHttpRequest();
     request.oprn('GET',url,true);
     request.responseType= 'arraybuffer';


//下面就是对音频文件的异步解析
request.onload = function(){
    context.decodeAudioData(request.response,function(buffer){
    dogBarkingBuffer = buffer;
},onError);
}

request.send();

}

这里的音频文件是二进制文件(不是文本),所以我们要设置responseType为 ArrayBuffer。想了解更多关于ArrayBuffer的东西你可以点这里XHR2


一旦音频文件被加载之后,他就可以缓存下来继续用于后面的解码,或者可以使用AudioContext的decodeAudioData()将其马上解码。这个方法会拿到放在request.response里面的Arraybuffer文件,并且异步的执行解码操作。(这个过程不会干扰js代码的主进程)。
当decodeAudioData()执行完毕之后,它就会继续执行一个将被解码出来的音频PCM文件作为参数的回调函数。


播放音频


WAA简易视图

当加载完AudioBuffer文件之后,我们就可以准备去播放音频文件了。假设我们已经加载一个 dog barking文件并且已经被解码完毕了。那么我们就可以通过以下的代码来实现音频的播放了。

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

function playSound(buffer){
     var source = context.createBufferSource();
     source = context.createBuffersource();//创建一个音频源 相当于是装音频的容器
    source.buffer = buffer;//  告诉音频源 播放哪一段音频
    source.connect(context.destination);// 连接到输出源
    source.start(0);//开始播放
}

当然,playSound()可以任意事件调用,比如设置一个按键,单击之后执行。


start(time)函数则可以精确的控制音频的播放以及回放,在一些游戏以及对音频播放时间非常严格的应用里面必定有非常大的用处。(不过在一些老旧的系统里 你需要使用的函数是noteOn(time)不是start(time))

深入使用 Web Audio Api

这里还有其他加载音频的方式,比如 BufferLoader class

以下就是使用BufferClass的例子。首先创建两个AudioBuffers;在他们加载完之后,开始播放他们。

window.onload = init;
var context;
var bufferLoader;

function init() {
    window.AudioContext = window.AudioContext||window.webkitAudioContext;
    context = new AudioContext();

bufferLoader = new BufferLoader(
    context,
[
   '../sounds/vdsbds/askhd/ashd.wav',
   '../hasjd/sadhasj/asdhasj.mp3'    // 仅做个示例 不要在意是乱打的
],
finishedLoading
);
bufferLoader.load();
}

function finishedLoading(bufferlist){
//创建两个音频源,并且同时播放
var source1 = context.createBufferSource();
var source2 = context.createBufferSource();

source1.buffer = bufferList[0]
source2.buffer = bufferList[1];

source1.connect(context.destination);
source2.connect(context.destination);


}

跟随时间舞蹈:按照节奏播放声音

开发者们也能够使用WAA产生有节奏的的声音。为了展示这项特性,我们可以创建一个简单的节奏音乐。以下的谱子可能是最常见的鼓点了。


一个简单的鼓点

在这个谱子里面一个hihat 每0.5拍响一次,一个kick和snare在每4拍响一次。假设我们已经加载了kick,snare,以及hihat的buffer,如下就是这首谱子怎么演奏的代码。

for(var bar = 0;bar <2;bar ++){
    var time = startTime + bar*8*eighthNoteTime;
// 在拍子在1,5的时候   kick响
playSound(kick,timeplaySound(kick,time+4*eigthNoteTime);
// 在 鼓点 3,7的时候 snare响
playSound(snare,time+2*eighthNoteTime);
playSound(snare,time+6*eighthNoteTime);

//  每一个8拍  hihat响
for(var i =0;i<8;++i){
playSound(hihat,time+i*eightthNoteTime);
}

}

在这个乐谱里面我们只做了一个简单的重复而不是无止境的循环。playsound是一个用来指定特定时间播放特定声音的函数,代码如下:

function playSound(buffer,time){
    var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
source.start(time);
}

改变一个声音的音量

对声音的操作之中,最普遍的应该就是改变一个声音的音量了。在WAA里面我们可以将音频元与一个GainNode相连,通过操作GainNode来达到这个目的。


GainNode

如下的代码可以完成以上的要求:

// 创建Gainnode
var gainNode = context.createGain();
//将音频源与GainNode连接
source.connect(gainNode);
//将GainNode与播放源连接
gainNode.connect(context.destination);

这些东西串联好了之后,你就可以操作GainNode来改变音量了

// 降音量
gainNode.gain.value = 0.5;

此处有一个完整的示例
点这里源代码

渐入渐出

现在,假比我们有一个略微或者比较复杂的剧本,里面会播放非常多的音频文件而且音频之间会有交叉的过渡。在一些类DJ的软件里面这种场景也时常出现,常常是两个唱片机,一个唱片机的音乐过渡到另一个唱片机的音乐。
下图将展示所描述的场景:


渐入渐出

为了建立这个场景,我们需要创建两个GainNodes,然后将他们连接到对应的source上面,用以下的代码就可以达到这个目的:

function createSource(buffer){
   var source = context.createBufferSource();
   // 创建一个gainNode
var gainNode = context.createGain();
source.buffer = buffer ;
// 开启循环模式
source.loop = true;
//将音频文件与gain连接
source.connect(gainNode);
// 将gainNode与输出源连接
gainNode.connect(context.destination);

return {
    source:source,
    gainNode:gainNode
};
}

线性过渡

通过改变音量的的大小可以做到一个简单的线性交叉过渡。


线性交叉过渡

为了解决这个问题,我们使用等功率曲线,其中相应的增益曲线是非线性的,并且以更高的幅度相交。这最小化音频区域之间的音量下降,导致在可能在级别上稍微不同的区域之间的更均匀的交叉淡入淡出。


等功率曲线

由于简书里面不能直接贴html标签
代码就放在这里吧 等功率曲线渐变

音乐切换之间的过渡

还有一个比较常见的过渡就是在音乐应用里面,音乐之间的切换一般也是渐入渐出的,这样做是为了避免音乐切换造成的突兀感。为此,我们可以使用定时器来定时调节音量来做到渐入渐出,但是这么做是不够精确的。在Web Audio Api里面,我们可以使用AudioParam 接口来深度的调节GainNode里的音量。

好了,设置一个播放列表,我们可以通过提前设置当前播放的音频减小音量,下一个要播放的音频增加音量来完成一个过渡,并且要让下一个音频略微的在前一个音频播放完之前开始播放。

function playHelper(bufferNow,bufferLater){
    var playNow = createSource(bufferNow);
    var source = playNow.Source;
    var gainNode = playNow.gainNode;
    var duration = bufferNow.duration;
    var currTime = context.currentTime;
// 开始播放 playNow
 source.start(0);
// 在音频的最后 将其 渐出
gainNode.gain.linearRampToValueAtTime(1,currTime+duration-ctx.FADE_TIME);
gainNode.gain.linearRampToValueAtTime(0,currTime+duration);
// 在音频完成之后做一个反转
var recurse = arguments.callee;
ctx.timer = setTimeout(function(){
    recurse(bufferLater,bufferNow);
    },(duration-ctx.FADE_TIME)*1000);
}

WebAudioApi 提供了很多 函数曲线式的逐步改变值得方法,比如上面的linearRampToValueAtTime 还有其他的比如exponentialRampToValueAtTime(指数式的改变)。

除了上面两种函数式之外,我们还可以自定义函数式来对值做出一些改变,使用setValueCurveAtTime函数就可以达到这个目的。

一下就是一个示例:
code


给音频添加 一个简单的滤波器


滤波器

WebAudioApi 让你的音频就像水管里面的水一样从一个AudioNode 导向另外一个,从而构成一个比较复杂,且完整的音频处理过程。
这里要说的,是对音频做一些效果上面的处理,我们用到的是BiquadFilterNode,将其放置在你的音频源和输出源之间,添加一些设置就可以做出很多效果出来。这种音频节点可以做出各种低阶模拟器的效果,其可以用于构件图形均衡器甚至更加复杂的效果,这与你选择强调哪些频谱抑制哪些频谱有关。

以下是支持的滤波器

  • 低通滤波器
  • 高通滤波器
  • 带通滤波器
  • 低架滤波器
  • 高架滤波器
  • 峰值滤波器
  • 陷波滤波器
  • 全通滤波器

桌面级音频应用示例

所有这些滤波器都含有特定的参数去限定增益。低通滤波器保持较低的频率范围,但是丢弃了高频。断裂点由频率值,以及Q因子是无单位的来确定,这也决定了图像的形状。增益仅影响某些滤波器,如低架滤波器和峰值滤波器,而不是此低通滤波器。

以下是一个简单的滤波器示例:

var filter = context.createBiquadFilter();
// 创建音频图像
source.connect(filter);
filter.connect(context.destinaton);
//  设置filter的参数
filter.type = 'lowpass';// 低通 滤波器 详情可以见 BiquadFilterNode的文档
filter.frequency.value = 440;// 设置截止位置为 440HZ
// 回放音频
source.start(0);

完整的示例代码
CODE
一般来说,频率控制需要调整到工作时的对数刻度,因为人类听觉本身工作在相同的原理(即,A4是440hz,和A5是880hz)。
最后,请注意,示例代码允许您连接和断开连接过滤器,动态更改AudioContext图形。我们可以通过调用node.disconnect(outputNumber)从图中断开AudioNodes。例如,要将图表从通过过滤器重新路由到直接连接,我们可以执行以下操作:

// 断开 音频源于 滤波器之间的连接
source.disconnect(0);
filter.disconnect(0);
// 音频源 直接与输出源连接
source.connect(context.destination);

后记

以上,所讲的东西已经覆盖了大部分WAA 的基本Api,包括了加载,播放音频文件,使用增益节点和滤波器绘制出了音频图像,设置音频的节奏,改变音频的参数,来达到一些常见的音效。 学会了这些,我们也可以做一些比较cool的音频应用了。

(可惜。。。Web Audio Api 普及太少,不像canvas一样为广大前端开发工程师所知,所以作品也比较少,这里我也没有什么音频WebApp 可以推荐的)