翻译自:https://webaudioapi.com/book/Web_Audio_API_Boris_Smus_html/ch05.html
到目前为止,我们只谈到了音频合成和处理,但这只是Web Audio API提供的一半功能。另一半,即音频分析,是关于理解正在播放的声音是什么样的。这个功能的典型例子是可视化,但还有许多远远超出本书范围的其他应用,包括音高检测、节奏检测和语音识别。
对于我们这些游戏开发者和交互式应用程序构建者来说,这是一个重要的话题,有几个原因。首先,一个好的视觉分析器可以作为一种调试工具(显然除了你的耳朵和一个好的计量设置外),用于调整声音,使之恰到好处。其次,可视化对于任何与音乐有关的游戏和应用都是至关重要的,从吉他英雄等游戏到GarageBand等软件。
频率分析
用Web Audio API做声音分析的主要方法是使用AnalyserNodes。这些节点不会以任何方式改变声音,可以放在你的音频环境中的任何地方。一旦这个节点出现在你的图中,它就为你提供了两种检查声波的主要方式:在时域和频域上。
你得到的结果是基于一定缓冲区大小的FFT分析。我们有几个旋钮来定制该节点的输出。
fftSize
这定义了用于执行分析的缓冲区大小。它必须是2的幂。更高的值将导致对信号的更精细的分析,但代价是一些性能损失。
频数BinCount
这是一个只读属性,自动设置为fftSize/2。
平滑时间常数
这是一个介于0和1之间的值。值为1会导致一个大的移动平均窗口和平滑的结果。值为0意味着没有移动平均,结果快速波动。
基本设置是将分析器节点插入到我们音频图的感兴趣的部分。
// 假设节点A通常与B相连
var analyser = context.createAnalyser();
A.connect(analyer);
analyser.connect(B)
然后,我们可以按以下方式获得频域或时域数组。
var freqDomain = new Float32Array(analyzer.frequencyBinCount)
analyser.getFloatFrequencyData(freqDomain)
在前面的例子中,freqDomain是一个对应于频域的32位浮点数数组。这些值被归一化为零和一之间。输出的索引可以在零和奈奎斯特频率之间进行线性映射,奈奎斯特频率被定义为采样率的一半(在Web Audio API中通过context.sampleRate提供)。下面的代码片段将频率映射到正确的频率桶数组中。
func getFrequencyValue(frequency) {
var nyquist = context.sampleRate/2;
var index = Math.round(frequency/nyquist * freqDomain.length)。
return freqDomain[index]。
}
例如,如果我们正在分析一个1000Hz的正弦波,我们会期望getFrequencyValue(1000)在图中返回一个峰值,如图5-1所示。
频域也可以通过 getByteFrequencyData 调用以 8 位无符号单位提供。这些整数的值被缩放以适应分析器节点上的minDecibels和maxDecibels(以dBFS为单位)属性之间,所以这些参数可以被调整以按需要缩放输出
Figure 5-1. A 1,000-Hz tone being visualized (the full domain extends from 0 to 22,050 Hz)
用requestAnimationFrame做动画
如果我们想为我们的声音形式建立一个可视化,我们需要定期查询分析器,处理结果,并渲染它们。我们可以通过设置一个JavaScript定时器(如setInterval或setTimeout)来做到这一点,但还有一个更好的方法: requestAnimationFrame。这个API可以让浏览器将你的自定义绘制功能纳入其本地渲染循环中,这是一个很大的性能改进。你不需要强迫它在特定的时间间隔内绘制,也不需要和浏览器做的其他事情争论,你只需要请求把它放到队列中,浏览器就会尽快处理它。
因为requestAnimationFrame API仍然是实验性的,我们需要使用取决于用户代理的前缀版本,并退回到一个粗略的等价物:setTimeout。这方面的代码如下。
window.requestAnimationFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60)。
};
})();
一旦我们定义了这个requestAnimationFrame函数,我们应该用它来查询分析器节点,给我们提供关于音频流状态的详细信息。
视觉化的声音
把这一切放在一起,我们可以设置一个渲染循环,像以前一样查询和渲染分析器的当前频率分析,进入一个freqDomain数组。
var freqDomain = new Uint8Array(analyzer.frequencyBinCount)。
analyser.getByteFrequencyData(freqDomain)。
for (var i = 0; i < analyser.frequencyBinCount; i++) {
var value = freqDomain[i];
var percent = value / 256;
var height = HEIGHT * percent;
var offset = HEIGHT - height - 1;
var barWidth = WIDTH/analyser.frequencyBinCount;
var hue = i/analyser.frequencyBinCount * 360;
drawContext.fillStyle = 'hsl(' + hue + ', 100%, 50%)';
drawContext.fillRect(i * barWidth, offset, barWidth, height)。
}
我们也可以为时域数据做一个类似的事情。
var timeDomain = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteTimeDomainData(freqDomain)。
for (var i = 0; i < analyser.frequencyBinCount; i++) {
var value = timeDomain[i];
var percent = value / 256;
var height = HEIGHT * percent;
var offset = HEIGHT - height - 1;
var barWidth = WIDTH/analyser.frequencyBinCount;
drawContext.fillStyle = 'black';
drawContext.fillRect(i * barWidth, offset, 1, 1)。
}
这段代码使用 HTML5 画布绘制了时域值,创建了一个简单的可视化器,在代表频域数据的彩色条形图上面渲染了一个波形图。其结果是一个帆布输出,看起来像图5-2,并随时间变化。
Figure 5-2. A screenshot of a visualizer in action