概述

目前为止,已经展示了2d绘图,动画以及3d硬件加速。当你使用这些技术的时候,你可能注意到一些东西缺失的:声音! 传统上如果不使用插件网页上要展示优质音频是不可能的,现在出现了一个新的音频API,就是 WebAudio改变了这种状况。

注意该API还在变化中,尽管比之前稳定了一点。使用WebAudio作为实验使用而非生产环境中,最少不要回退到Flash。 试试 SoundManager2作为备用解决方案。

Audio元素 vs WebAudio

你应该听过Audio 元素。这是个新增加到H5中的新元素,看起来这样:<audio src="music.mp3"/> 。Audio元素播放音乐非常友好。与使用图片一样使用Audio即可。浏览器会展示控制播放的开关控件。 同时它也包含一个小小的JS API。然而Audio元素只是用来播放音乐而已。播放短小的音效和多个声效并不容易,而你只能每次播放一个。 最重要的一点是你不能生成音频或者存取音频样本进一步处理。 Audio元素适合做:播放音乐,但又很大的限制。

为了解决这些短板问题,浏览器厂商制定了WebAudio接口的技术文档。 它定义了一个完整的声频处理接口,包括音频生成,过滤,接收以及采样。如果只想播放音频的就使用Audio元素。 需要更多控制处理的就使用WebAudio。

完整的WebAudio接口文档太多了,这个章节是讲不完的。所以我只会讲对Canvas开发者感兴趣的部分: 声效和可视化处理。

简单回放

对于图像处理使用图像上下文。对应Audio来说,也是一样。我们需要一个audio上下文。 由于文档还不是正式标准,我们使用 webkitAudioContext()。 确定页面加载完毕之后再创建初始化音频系统。

var ctx; // 音频上下文
var buf; // 音频缓存

// 初始化音频系统
function init() {
    console.log("in init");
    try {
        ctx = new webkitAudioContext(); // 现在可以使用AudioContext了
        loadFile();
    } catch(e) {
        alert('you need webaudio support');
    }
}
window.addEventListener('load',init,false);

创建音频上下文之后,就可以加载声音了。加载声音就像加载其他远程的资源一样,使用XMLHttpRequest。 我们需要设置类型为 'arraybuffer' 而不是 text, xml 或者JOSN。 由于JQuery 还不支持 'arraybuffer' [不确定?],我们直接使用XMLHttpRequest API。

//加载并解码mp3文件
function loadFile() {
    var req = new XMLHttpRequest();
    req.open("GET","music.mp3",true);
    req.responseType = "arraybuffer";
    req.onload = function() {
        // 解码加载的数据
        ctx.decodeAudioData(req.response, function(buffer) {
            buf = buffer;
            play();
        });
    };
    req.send();
}

文件加载之后,需要解码到一个音频缓存区里。上面的代码已完成该功能,还包括了另一个回调函数play。 解码之后播放音频。

// 播放加载文件
function play() {
    // 从缓存中创建一个源节点
    var src = ctx.createBufferSource(); 
    src.buffer = buf;
    // 连接最终的输出代码(扬声器)
    src.connect(ctx.destination);
    // 马上播放
    src.noteOn(0);
}

我打算详细地过上面的代码片段,因为这对于你明白这里完成什么功能非常重要。

WebAudio中的一切都围绕着节点(nodes)。 为了处理声音,需要将节点绑定到一个链表或图中,然后开始处理。 回放音频,需要source节点以及detination节点。 ctx.createBufferSource()创建source节点,然后将audio音频缓存设置到声音上。 ctx.destination是一个包含标准目标输出的属性,通常就是电脑的扬声器。 以上两个节点使用 connect 函数连接。连接之后就能调用源节点的函数notOn(0)来播放声音。

WebAudio 节点

目前为止使用了一个源source和目标destination节点,当让,WebAudio拥有很多其他的节点类型。 创建一个打鼓app,你需要创建多个source源节点,每一个鼓需要一个,连接到输出使用AudioChannelMerger。 控制每个鼓声使用AudioGainNode

更多WebAudio 节点:

声音效果

标准的audioHTML 元素也可以用来做声音特效,但效果并不是很好。 关于声频怎样和什么时候播放你没有太多的精确控制。某些实现效果还不能让你一次播放多个声频。 对于歌曲来说确实是够了,但对于游戏中的声音特效确实远远不够的。WebAudio API让你能够进行声音剪辑, 让声音在精确的时间播放并重复叠加。

播放单一声音多次,我们不需要做额外的工作。 我们只选哟创建多个缓冲源。 以下代码定义了play函数,用来创建缓冲源,每次被调用就会立即播放。

//play the loaded file
function play() {
    // 从缓存中创建源节点
    var src = ctx.createBufferSource(); 
    src.buffer = buf;
    // 连接最终的输出节点 (扬声器)
    src.connect(ctx.destination);
    // 立即播放
    src.noteOn(0);
}

这里查看demo。 每次按下按钮,就会播放一个简短的镭射声音。(来自inferno on freesound.org) 当你每次快速按下按钮,你就听见能正确地叠加在一起。 我们没有做额外的工作处理实现这种情况。Web Audio自动处理的。 在游戏中,每次一个人物开枪的时候我们就调用play函数。如果四个玩家同时开枪,你会听见正确的声音。

我们也可以通过覆盖声音创建新的音效。noteOn()函数延迟一定的时间(秒)播放声音。 通过播放镭射片段四次,每次偏移1/4秒,来创建新的声音。这样就会清晰地叠加,并创建了一个新的音效。

var time = ctx.currentTime;
for(var i=0; i<4; i++) {
    var src = ctx.createBufferSource(); 
    src.buffer = buf;
    // 连接最终的输出节点 (扬声器)
    src.connect(ctx.destination);
    // 立即播放
    src.noteOn(time+i/4);
}

注意我们将当前的时间加上audio上下文的偏移时间,得到每个片段的偏移时间。

尝试 这里 的最终版本

Audio 可视化

如果不能将音效输出到图形显示器上,有什么意思?! 我喜欢声音可视化。如果你曾使用WinAmp或者itunes可视化工具,那么你应该就会很熟悉。

所有的可视化器的工作流程都完全一样:动画的每一帧都获取当前声音的比率分析,然后通过某种有趣的方式绘制频率。 WebAudio API使用RealtimeAnalyserNode实现起来很简单。

首先,我们跟之前一样加载audio。我添加到了几个额外的变量, fft, samplessetup

var ctx; // audio 上下文
var buf; // audio 缓冲
var fft; // fft audio 节点
var samples = 128;
var setup = false; // 指示audio是否准备好


// 初始化声音系统
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);

// 加载mp3文件
function loadFile() {
    var req = new XMLHttpRequest();
    req.open("GET","music.mp3",true);
    // 因为需要设置'arraybuffer'类型,所以不使用jQuery
    req.responseType = "arraybuffer";
    req.onload = function() {
        // 解码加载数据
        ctx.decodeAudioData(req.response, function(buffer) {
            buf = buffer;
            play();
        });
    };
    req.send();
}

之前我们使用源和目标节点来播放音乐。 这次我们在他们之间使用一个analyser节点。

function play() {
// 从buffer中创建一个源节点
var src = ctx.createBufferSource(); 
src.buffer = buf;

// 创建 fft
fft = ctx.createAnalyser();
fft.fftSize = samples;

// 通过连接关联
src.connect(fft);
fft.connect(ctx.destination);

// 立即播放
src.noteOn(0);
setup = true;
}

上面的函数使用createAnalyser创建一个analysis节点。

I've called the analyser node fft which is short for a Fast Fourier Transform.

我已经调用analyser节点fft,它其实是快速傅立叶变换简写。

马上进入到疯狂的声音数学中

如果你看一下含有声音的缓冲区,你会看到只有一堆样本,很可能每秒有四十四个样本。 它们代表离散的振幅值。做音乐可视化我们不想要直接的样本,而是波形。 当你听到一个特定的音调时,你真正听到的是一堆重叠的波形,随着时间的推移被切割成振幅的样本。

我们想要一个频率的列表,而不是振幅,所以我们需要一种方法来转换它。声音从时间域开始。 离散傅立叶变换从时域转换到频域。 快速傅立叶变换(FFT)是一种特别的算法,可以很快地完成转换。 要做到这一点的数学可能很棘手,但Chrome团队中的聪明人已经在分析器节点中为我们做了这件事。 我们只需要在需要的时候取最后的值。

关于离散傅立叶变换以及快速傅里叶变换的完整的解释,请看 维基

绘制频率

现在绘制一点东西。回想在动画章节我们所学的。创建一个画布,获取上下文,每一帧中调用绘制函数。

var gfx;
function setupCanvas() {
    var canvas = document.getElementById('canvas');
    gfx = canvas.getContext('2d');
    webkitRequestAnimationFrame(update);
}

需要一个变量来存储音频数据。我们使用一种Javascript的新数据类型Uint8Array用来支持音频及3d。 跟Javascript可以存放任何类型的数组不同,一个Uint8Array被设计成只能存储无符号八位二进制数,例如:一个比特数组。 JavaScript引入了这些新的数组类型,支持对二进制缓冲区、音频样本和视频帧等二进制数据的快速访问。获取这些数据我们调用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位置基于样本数据的当前值。 由于我们使用的是Uint8Array每个值在0和255之间,所以我让它乘以二使运动增大。下面是它的样子:

例子Music Bars ( 运行)

从128个实时FFT样本中绘制的矩形

对于只有几行的Javascript来说,这并不坏。(我还不确定后半部分为什么是平的。或者是一个立体声/单声道的bug?) 这是一个豪华版。音频代码是一样的,我只是改变了如何绘制样本。

例子WinAMP 样式可视化器 ( 运行)

从128个实时FFT样本中通过复制拉伸绘制的线

下一步

使用WebAudio有很多比这里所讲的能做的。首先,我建议你浏览一下HTML5 Rocks 的教程:

接下来看看 0xFE's 使用Web音频API生成音调 学习如何直接从数学波形中生成声音。 以及 网络音频频谱分析仪.

完整 (草案) WebAudio 文档

下一章节中我们会获取用户的摄像头(webcam)。