2

关于Web Audio中Analyzer输出的频率数据分布问题

 2 years ago
source link: https://jw1.dev/2022/08/19/a01.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

关于Web Audio中Analyzer输出的频率数据分布问题

发布时间: 2022-08-19

随着iOS 16 beta 5的更新,细心的小伙伴可能注意到了,系统在播放音乐时,音乐控制台的右上角多了一个小小的频谱显示器。对我这种喜欢音乐的人来说,看着它随着音乐一起动起来,心情似乎也变得好起来。那么问题来了,浏览器上是否可以实现这样的功能?答案是可以,但是似乎有一点小问题。

Untitled.jpeg

我们先抛开顾虑,直接查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
}
}
})

这样一通操作下来,一个简单的频谱显示器就完成了(老有成就感了),大概原理也很简单:

  1. 创建一个音频 上下文Audio Context
  2. 使用上下文映射 <audio> 并赋值为 audioSrc
  3. 创建 分析仪 节点(Analyzer Node
  4. audioSrc 连接 分析仪分析仪 再连接到 ctx.destination 即用户端输出
  5. 图形化 分析仪 的数据

现在,真正的问题来了:不管我尝试什么音乐,我的频谱图永远都是这样的 ⬇️(成就感啪一下没了)

Untitled

可能会有一些小差异,但是呈现的效果基本差不多:低频看起来很足,高频几乎没有

这时候,作为一名“准专业编曲师”,我有一种直觉,Web Audio API 给出来的频谱数据,可能是线性分布的。为了验证我的猜想,我制作了一段从 30Hz 到 20000Hz 慢慢上升的正弦波音频,调高分析仪的 fftSize ,放进代码里看看输出:

再来看看同样的音频在 Ableton Live 中 Spectrum 线性模式下的输出:

这表现可以说,基本一致,同时也验证了我的猜想 —— Web Audio API 输出的频谱数据是线性分布的。线性分布是什么分布?很好理解:一个坐标系中,X轴上10到20需要走的距离,与2000到2010需要走的距离一样。然而对于人来讲,线性分布这个解决方案,并不是最优的。我们可以先看一些专业EQ插件的截图,看看他们是怎么做频谱分布的。

FabFilter Pro Q3:

Untitled

Eiosis Air EQ Premium:

Untitled

SlateDigital Inf EQ:

Untitled

Ableton Live EQ Eight

Untitled

可以看到,这些分布基本都是在类似指数分布的频率上进行分段式的对数分布,而且似乎是遵循着等响曲线做出的优化。

我们经常听到“指数级增长”这个词,举个简单的例子: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

现在再来看一下对比:

Untitled

Untitled

哪个体验更好,一眼看过去便知道了。

至此,一个不算完美但是看起来还不错的频谱显示器就算是完成了,我也给 webAudio 开源项目提了issue,希望未来能够添加支持,为开发者带来更佳的体验!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK