跳到主要内容

🎵 音频可视化与ThreeJS的开发

Demo: music.suningyao.com

Repo: github.com/fewwwww/music.suningyao.com

前言​

Gonsa 这个项目是我在 NYU IDM 的 WebGL 课程的期末作业, 为一个音频可视化网站, 对我的多首精选原创音乐进行了可视化.

网站采用了 ThreeJS 作为 3D 引擎, TypeScript 作为语言, Vite 作为工具链, MVC 作为设计模式, Web Audio API 作为音频处理, SimplexNoise 辅助可视化生成.

设计​

设计上, 由于对自己的设计功力的不自信, 我一开始就决定减少不必要的设计, 只保留最少的元素. 同时没有做额外的音乐播放器等样式.

颜色上, 我选择了黑与白两种颜色, 让视觉更加清晰. 布局上, 整个项目仅有一个按钮, 以及两个页面中的不同 3D 图形. 交互上, 我为唯一的按钮设计上了点击效果以及悬停效果.

图形的构建上, 我采用了非常多的线条, 虽然都是 3D 图形, 但没有任何大的色块, 而是仅剩一个核心的骨架, 为画面添加了更多的留白.

图形的动效上, 我让起始页的图形随机变化长度, 主页面的球形随着音乐变化律动.

交互上, 用户的鼠标位置会引起视角的变化.

音乐上, 我并没有选择用户上传自己的音乐, 而是指定了我的音乐, 这是为了避免过多的用户操作以及风格的统一. 音乐的选取我初步选择了一些自己喜欢的原创的音乐, 并且挑选出了几个展示效果明显的.

代码上, 我尽力简化了代码行数, 文件数等.

细节​

1. 起始页图形形状​

虽然是用的 ThreeJS 的 3D 框架, 但是起始页的图形看着像是线条, 这是因为为了设计上的美观, 我让这几个长方体的宽度和深度都变成 0 了.

// add one bar
addBar(x: number): void {
const bar = new BoxGeometry(0, 50, 0);
const material = new MeshPhongMaterial({ color: 0xffffff, wireframe: true });
const tempBar = new Mesh(bar, material);
// ...

2. 起始页图形动效​

由于如果完全正经随机的话, 很多情况下, 会有一些图形很幸运地越变越长直到充满屏幕. 所以变大变小的机率并非是 50%, 而是变小的几率会略大于变大, 这样图形就会和音波一样越来越小, 直到消散.

// updating the bars size
update(): void {
this.bars.forEach((bar) => {
if (Math.random() > 0.7) {
// random number between 0 and 1
// it is 0.501 because we need the bars lower in general after a lot of trials
Math.random() > 0.501 ? this.up(bar) : this.down(bar);
}
});
}

还有就是切页面的时候要 call 一下 onPointerMove, 这样避免切的渲染时候突然会从 (0, 0) 跳到指针位置. 以及手机上没有 onmouseleave, 所以在 onclick 的时候也需要把页面的反色恢复.

playDOM.onclick = function (event: any) {
// ...
model.activeView = (model.activeView + 1) % views.length;
// make camera not jumping
onPointerMove(event)
};

3. Web Audio API 使用​

由于浏览器限制, 我们无法直接播放音频, 所以需要按钮来触发音频的发声. 同时因为我们的项目分了组件, 所以我们挂了两个点击事件给两个组件.

主组件 (播放音乐)

// ...
// initiate the audio on clicking, hide the play button, change active view
playDOM.onclick = function () {
audioDOM.src = model.audioSrc[Math.floor(Math.random() * model.audioSrc.length)];
};
// ...

子组件 (创建 context)

// ...
if (htmlDOM) {
// we need an event to get the audio context
htmlDOM.onclick = function () {
// web audio api stuffs
const context = new AudioContext();
// ...

4. 按钮样式​

由于按钮的 position 是 fixed, 同时需要水平居中, 同时需要有 active 的大小变换动画, 同时要有移动端适配. 所以, 如果要考虑这些所有的因素的话, 最好的方法就是把宽度也设置为 vh 单位... 你可以仔细想想, 还有没有办法能同时满足这些.

/* ... */
#play {
width: 30vh;
height: 10vh;
transition: all cubic-bezier(0.165, 0.84, 0.44, 1) 0.5s;
position: fixed;
/* ... */
}
/* ... */
#play:active {
transform: scale(0.95);
}

hover 上去的特效其实也很有趣. hover 上去会有一个全界面反色, 点击进去之后也会有一个小小的延迟, 就会看到球的那个页面也是稍微有点黑的, 然后迅速变白. 这个是无心插柳柳成荫的一个效果.

5. console 的 log​

console 里面会 log, 不对, info 出一些小信息呀.

<script>
console.info('🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵')
console.info('made by Suning Yao')
console.info('🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵🎵')
console.info('learn more about me at suningyao.com')
</script>

6. 音乐文件​

一开始是想都用 .flac 格式的, 因为这样音质最高. 但是其实 .mp3 格式没啥显著区别, 大小还小一点.

参考资料​

  1. 我的音乐人账号
  2. 项目的主要逻辑部分参考这个博客
  3. 更好看的一个音乐可视化项目