随着 iOS 16 beta 5 的更新,细心的小伙伴可能注意到了,系统在播放音乐时,音乐控制台的右上角多了一个小小的频谱显示器。对我这种喜欢音乐的人来说,看着它随着音乐一起动起来,心情似乎也变得好起来。那么问题来了,浏览器上是否可以实现这样的功能?答案是可以,但是似乎有一点小问题。
我们先抛开顾虑,直接查 MDN 堆代码看看效果。
<div id="app">
<audio id="music" src="test-2.mp3"></audio>
<button @click="play">play</button>
<div style="display: flex; height: 60px; align-items: flex-end; margin-top: 20px">
<div class="item" v-for="item in fData" :style="{height: item / 255 * 100 + '%'}"></div>
</div>
</div>
<script src="vue.js"></script>
.item {
width: 6px;
min-height: 6px;
background: #333333;
align-items: flex-end;
border-radius: 6px;
margin-right: 3px;
}
new Vue({
el: '#app',
data: function () {
return {
fData: []
}
},
methods: {
play() {
let _ = this
let audio = document.getElementById('music')
audio.play()
let audioContext = new AudioContext()
let audioSrc = audioContext.createMediaElementSource(audio)
let analyzer = audioContext.createAnalyser()
analyzer.fftSize = 32
audioSrc.connect(analyzer)
analyzer.connect(audioContext.destination)
let bufferLength = analyzer.frequencyBinCount
let frequencyData = new Uint8Array(bufferLength)
setInterval(() => {
analyzer.getByteFrequencyData(frequencyData)
_.fData = _.uint8ArrayToArray(frequencyData)
}, 1000 / 24)
},
uint8ArrayToArray(uint8Array) {
let array = []
for (let i = 0; i < uint8Array.byteLength; i++) {
array[i] = uint8Array[i]
}
return array
}
}
})
结果:
这样一通操作下来,一个简单的频谱显示器就完成了(老有成就感了),大概原理也很简单:
- 创建一个音频
上下文
(Audio Context) - 使用上下文映射
<audio>
并赋值为audioSrc
- 创建
分析仪
节点(Analyzer Node) audioSrc
连接分析仪
,分析仪
再连接到ctx.destination
即用户端输出- 图形化
分析仪
的数据
现在,真正的问题来了:不管我尝试什么音乐,我的频谱图永远都是这样的 ⬇️(成就感啪一下没了)
可能会有一些小差异,但是呈现的效果基本差不多:低频看起来很足,高频几乎没有
这时候,作为一名 “准专业编曲师”,我有一种直觉,Web Audio API 给出来的频谱数据,可能是线性分布的。为了验证我的猜想,我制作了一段从 30Hz 到 20000Hz 慢慢上升的正弦波音频,调高分析仪的 fftSize ,放进代码里看看输出:
再来看看同样的音频在 Ableton Live 中 Spectrum 线性模式下的输出:
这表现可以说,基本一致,同时也验证了我的猜想 —— Web Audio API 输出的频谱数据是线性分布的。线性分布是什么分布?很好理解:一个坐标系中,X 轴上 10 到 20 需要走的距离,与 2000 到 2010 需要走的距离一样。然而对于人来讲,线性分布这个解决方案,并不是最优的。我们可以先看一些专业 EQ 插件的截图,看看他们是怎么做频谱分布的。
FabFilter Pro Q3:
Eiosis Air EQ Premium:
SlateDigital Inf EQ:
Ableton Live EQ Eight
可以看到,这些分布基本都是在类似指数分布的频率上进行分段式的对数分布,而且似乎是遵循着等响曲线做出的优化。
我们经常听到 “指数级增长” 这个词,举个简单的例子:10、100、1000、10000,这个数列就是指数增长。上述频谱图中,频率分布基本是按照指数来的。
关于对数我们不需要了解太多,对数分布也可以根据上面我对线性分布的理解做出延伸:一个坐标系中,X 轴上 10 到 20 需要走的距离,与 2000 到 2010,同样是 10 的差距,但是需要走的距离却不一样。如果我们以 Ableton Live 更加青睐的分布规则,把频谱分析仪的可视化面板看作是一个坐标系,则 X 轴上,10 - 10000 被分成三段,分别为 10 - 100、100 - 1000、1000 - 10000,每一段距离都一样且被分成 9 份进行对数分布。
说到等响曲线,它并不像对数那样客观,而是一个主观概念。此概念阐述的内容如果用大白话来讲就是:同样音量但不同频率的声音,给人听起来的强弱是不一样的。即:60Db 的 4000Hz 正弦波与 60Db 的 10000Hz 正弦波,前者听起来会更响一些。此概念也同样可以用来解释,在删除歌曲 10kHz 以上部分的时候(可以理解为对半切),你不会觉得整首歌的质量下降了 50%,主观上听起来可能只有 10%。
个人认为,频谱这样的分布模式,其实是为了弥补人耳的缺点,在视觉上模拟出与听觉类似的 “缺陷”,真正做到音视一致从而提升用户体验。
关于如何在浏览器里实现,其实很简单,我们只需要模拟指数级分布就好,不用去考虑分段中的对数分布。演示地址: https://jw1.dev/frequency-test/test-2.html
现在再来看一下对比:
哪个体验更好,一眼看过去便知道了。
至此,一个不算完美但是看起来还不错的频谱显示器就算是完成了,我也给 webAudio 开源项目提了issue,希望未来能够添加支持,为开发者带来更佳的体验!
✌️