第九章 WebAudio简介
概述
到目前为止,我已经向你展示了2D绘图,动画,以及硬件3D加速。当你用这些技术构建app时,你会发现还是缺少很重要的一环,声音!传统意义上,如果不用插件,web上的音频效果可以用恐怖或者不可能形容,但是这种局面随着新的音频apiWebAudio
的出现而得以改变。
需要注意的是,这个API依旧在测试状态,不过目前已经很稳定了。
Audio元素 vs WebAudio
Audio元素在用于播放音乐时非常好。但是使用它播放短音频并不容易,而且大多数浏览器只允许你同时播放一断音频。更重要的是,你不能操作音频。相比较WebAudio,其是受限制的。
WebAudio
具有完备的生成,过滤,sinks和sample access。
完整的WebAudio API使用较为复杂,所以这里我们这里只阐述Canvas开发者可能感兴趣的部分,音频效果和可视化的进程。
简单的录播装置
对于图形来说,我们使用图形上下文。对于Audio
来说也是类似的,我们需要用到audio 上下文。由于这一部分还不是标准,所以我们需要使用webkitAudioContext()
,初始化声音系统需要一段时间,请确保在页面加载完成后创建。
var ctx; //audio context
var buf; //audio buffer
//init the sound system
function init() {
console.log("in init");
try {
ctx = new webkitAudioContext(); //is there a better API for this?
loadFile();
} catch(e) {
alert('you need webaudio support');
}
}
window.addEventListener('load',init,false);
一旦上下文加载成功,我们就可以加载音频了,加载音频和方法和加载其它远程资源的方法类似,可以使用XMLHttpRequest
,需要注意的是 我们必须设置类型为arraybuffer
,而不能使text
,xml
或者JSON
.由于jQuery是不支持arraybuffer
的,我们需要直接调用XMLHttpRequest
API .
//load and decode mp3 file
function loadFile() {
var req = new XMLHttpRequest();
req.open("GET","music.mp3",true);
req.responseType = "arraybuffer";
req.onload = function() {
//decode the loaded data
ctx.decodeAudioData(req.response, function(buffer) {
buf = buffer;
play();
});
};
req.send();
}
一旦文件加载成功,它会被分解为raw sound buffer。上述代码通过另外一个回调函数完成这个。一旦分解成功,我们就可以播放音频了。
//play the loaded file
function play() {
//create a source node from the buffer
var src = ctx.createBufferSource();
src.buffer = buf;
//connect to the final output node (the speakers)
src.connect(ctx.destination);
//play immediately
src.noteOn(0);
}
上面代码非常重要,我想好好的解释一下,以便你明白这里究竟发生了什么。
WebAudio
中的一切都涉及到一个被称作nodes
的概念。为了操作音频,我们把nodes拼成链条或者形状,为了实现简单的音频回播,我们需要一个源节点和一个目标节点。
ctx.createBufferSource()
方法创建了连接到音频缓存的源节点。ctx.destination
包含了目标输出,这个通常指的是电脑的扬声器。这两个节点由connect
方法连接起来。一旦连接起来,我们可以使用noteOn(0)
方法播放这段音频。
WebAudio Nodes
到目前为止,我们还只是涉及到了源节点和目标节点,但是WebAudio
其实具备很多其它类型的节点,为了创建一个鼓的应用,我们可以操作多重源节点,每一个对应一面鼓,通过AudioChannelMerger
Api 连接到一个输出处。我们也可以调用AudioGainNodes
改变每一面鼓的增益。
一些其它的WebAudio
节点:
JavaScriptAudioNode
: 使用JavaScript直接处理;BiquadFilterNode
: 低高处理器;DelayNode
: 时间延迟;ConvolverNode
: 混声实时线性效果;RealtimeAnalyserNode
: 声音可视化会用到的节点;AudioPannerNode
: 操作多声段,立体声的节点;AudioChannelSplitter and AudioChannelMerger
Oscillator
: 直接产生波形;
声音效果
常规的HTML audio
元素也可以应用声音效果,但是效果并不是很好,你对如何以及何时播放音频并没有太多的控制权,一些执行环境甚至不让你同时执行多个音频,用它来播放音乐还行,用来处理游戏中的音频就太弱了,webAudio
API 为你提供了在特定的时间播放准确的音频片段,甚至覆盖音频片段的能力。
为了让一段音频重复多次,我们并不需要做什么特殊处理,我们只需要构建多重的缓冲源。下述代码定义了一个播放函数,这个函数在调用的时候构建了缓冲源并会立即播放它。
//play the loaded file
function play() {
//create a source node from the buffer
var src = ctx.createBufferSource();
src.buffer = buf;
//connect to the final output node (the speakers)
src.connect(ctx.destination);
//play immediately
src.noteOn(0);
}
你可以在这里尝试效果,每一次你点击按钮会播放一段短的激光声 Freesound.org - “hilas.wav" by inferno ,如果你快速的点击按钮,你会听到声音准确的堆叠和覆盖。我们不需要做特殊的操作就实现了这个效果,WebAudio会自动处理它。在游戏中,我们在一个角色扣动扳机的时候可以执行play
函数,就算是四个角色同时扣动扳机,声音也会和我们的预期一样发出。
我们也可以通过合适的重叠声音构建新的声音。nodeOn()
函数使用时间戳来播放声音。为了合成一段新的声音,我们可以播放四次激光声音片段,每次偏移1/4秒。这样就能清楚的重叠。
var time = ctx.currentTime;
for(var i=0; i<4; i++) {
var src = ctx.createBufferSource();
src.buffer = buf;
//connect to the final output node (the speakers)
src.connect(ctx.destination);
//play immediately
src.noteOn(time+i/4);
}
请注意,我们需要在当前音频上下文中添加当前时间到偏移量中以获得每个片段的最终时间。 点击这里听听最终的效果
音频可视化
如果图形变化不伴随着音乐,那还有什么乐趣呢。我真的太喜欢音频可视化了,如果你用过WinAmp或iTunes的可视化界面,你对这个也很熟悉。
所有的图形可视化工具的运行原理都很类似,对于动画的每一帧,对当前音频进行快速的分析,然后把分析结果以一种有趣的方式绘制出来。通过使用RealtimeAnalyserNode
WebAudio API使得这一过程很简单。
首先我们像之前一样加载音频,这里我添加了名为fft,samples,setup
的额外变量
var ctx; //audio context
var buf; //audio buffer
var fft; //fft audio node
var samples = 128;
var setup = false; //indicate if audio is set up yet
//init the sound system
function init() {
console.log("in init");
try {
ctx = new webkitAudioContext(); //is there a better API for this?
setupCanvas();
loadFile();
} catch(e) {
alert('you need webaudio support' + e);
}
}
window.addEventListener('load',init,false);
//load the mp3 file
function loadFile() {
var req = new XMLHttpRequest();
req.open("GET","music.mp3",true);
//we can't use jquery because we need the arraybuffer type
req.responseType = "arraybuffer";
req.onload = function() {
//decode the loaded data
ctx.decodeAudioData(req.response, function(buffer) {
buf = buffer;
play();
});
};
req.send();
}
我们也是使用和前面一样的方法通过设置源节点和目标节点播放音频,不过这次我们在二者之间添加了一个分析节点。
function play() {
//create a source node from the buffer
var src = ctx.createBufferSource();
src.buffer = buf;
//create fft
fft = ctx.createAnalyser();
fft.fftSize = samples;
//connect them up into a chain
src.connect(fft);
fft.connect(ctx.destination);
//play immediately
src.noteOn(0);
setup = true;
}
分析节点fft
是Fast Fourier Transform
的简称。
如果你转向去看包含声音的缓冲,你会看到一束样本,大致是每秒四万四千个样本。它代表着不相关联的丰富的信息。为了做音频可视化,我们并不需要直接的样本而是波形的样本。当你听到一段特殊的曲调时,你实际听到的是随时间推移丰富的样本叠合而成的波形。
我们希望有的是由频率构成的列表,而不是丰富程度,我们还需要一种方法转换二者。声音发生在时间领域,使用分离Fourierv转换,可以实现从时间域到频率域的转换。快速Fourier转换(FFT),是一种可以快速完成这种转换的特殊算法。使用数学方法是很有技巧的,但是聪明的谷歌Chrome开发团队已经在分析节点中为我们完成了这个,当我们需要使用时,我们只需要取得最后的值即可。
绘制频率图
现在让我们绘制一些东西吧,我们复习一下我们在动画那一节学到的内容,创建canvas,获得上下文,然后在每一帧调用绘制函数。
var gfx;
function setupCanvas() {
var canvas = document.getElementById('canvas');
gfx = canvas.getContext('2d');
webkitRequestAnimationFrame(update);
}
为了获取音频数据,我们需要在一个地方防止它。我们需要使用Uint8Array
,这是一种用于支持音频和3d的新的JavaScript类型。和普通的JavaScript数组的不同之处在于它可以包含任何事物,Uint8Array被设计用来存储未签名的8位整数,或者你可以把它当做字节数组。JavaScript引入这种类型的原因在于支持快速处理3D buffers,音频片段,视频片段等二进制数据。我们可以使用fft.getByteFrequencyData(data)
来获取相关数据。
function update() {
webkitRequestAnimationFrame(update);
if(!setup) return;
gfx.clearRect(0,0,800,600);
gfx.fillStyle = 'gray';
gfx.fillRect(0,0,800,600);
var data = new Uint8Array(samples);
fft.getByteFrequencyData(data);
gfx.fillStyle = 'red';
for(var i=0; i<data.length; i++) {
gfx.fillRect(100+i*4,100+256-data[i]*2,3,100);
}
一旦我们获取到了这些数据,我们就可以开始绘制了。在这里,我绘制一系列的柱状图来表征这些数据,这些柱状图的y坐标依据当前样本数据的值来改变,由于我们用的数据类型为Unit8Array
,我们的值的范围在于0到255之间,我这里把他们的大小乘以了2,使得波动看起来更加明显,效果图如下:
下面是一个复合版本,音频的处理代码是一样的,在此我只是改变了绘制样本的方法。
下一步
除了我在这里介绍的,使用WebAudio
我们还可以做更多的事情。你可以从下面的资料学到更多
- Getting Started with Web Audio API - HTML5 Rocks
- Developing Game Audio with the Web Audio API - HTML5 Rocks
- 音频转换算法详解
下一章我们学习使用webcam.