音频API是一组提供给网页开发者的接口,允许他们直接在浏览器中处理音频内容。这些API使得在不依赖任何外部插件的情况下操作和控制音频成为可能。
Web Audio API 可以进行音频的播放、处理、合成以及分析等操作。借助于这些工具,开发者可以实现自定义的音效处理,创建互动的音乐体验,甚至开发复杂的音频应用程序,如实时音频频谱分析或音频可视化效果。
本文就通过Web Audio API来实现对音乐可视化的案例
基础结构
整个界面结构比较简单,需要一个播放音频的audio和用于可视化的canvas。
<canvas id="canvas"></canvas>
<audio
src="https://resource.dengzhanyong.com/audio/海底.mp3"
controls
></audio>
初始化工作
对 audio 注册播放事件,在首次播放时做一些音频处理相关的初始化工作
const audio = document.querySelector("audio");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// 事件初始化状态
let isInit = false;
// 绑定播放事件
audio.onplay = () => {
// 初始化内容只需要做一次,每次暂停后再播放都会调用此事件,因此这里做个判断
if (isInit) return;
/* 处理内容 */
isInit = true;
};
音频处理流程
要想把音频可视化,必须要拿到播放的音频数据,这就需要使用到AudioContext API,可以理解为是一个音频上下文,音频所有的事情都在上下文中发生,整个流程见下图。
源节点:可以通过AudioContext来创建源节点,本案例中源节点就是audio标签
处理节点:在这里对音频进行处理,比如:处理音色、音调、音频等
输出节点:一般为当前使用的扬声器,可以在AudioContext中获取到
每个节点之间通过连接的方式串联起来,形成一个完整的链路,在处理节点可以加入多个处理节点,也可以有多个源节点。
对于本案例来说,需要一个分析器节点,从分析器节点中获取到音频波形数据,然后处理数据,最后交给canvas绘制。
audio.onplay = () => {
if (isInit) return;
// 创建音频上下文
const audioCtx = new AudioContext();
// 创建音频源
const source = audioCtx.createMediaElementSource(audio);
// 连接到输出设备
source.connect(audioCtx.destination);
isInit = true;
};
audioCtx.destination为当前的输出设备
获取处理音频数据
使用audioCtx.createAnalyser 创建一个分析器节点,然后将源节点连接到分析其节点。
分析器节点获取到的是时域图的数据,需要通过快速傅立叶变换把时域图转为频域图数据,转换过程不需要我们自己去做,AudioContext 提供了相关的API,只需要简单设置一些参数即可。
// 设置初始化状态
let isInit = false;
let analyser, data;
// 绑定播放事件
audio.onplay = () => {
// console.log("开始播放");
if (isInit) return;
// 创建音频上下文
const audioCtx = new AudioContext();
// 创建音频源
const source = audioCtx.createMediaElementSource(audio);
// 创建分析器节点
analyser = audioCtx.createAnalyser();
// 设置窗口大小,窗口越大,分析结果越详细
analyser.fftSize = 512;
data = new Uint8Array(analyser.frequencyBinCount);
// 将源连接到分析器节点
source.connect(analyser);
// 将分析器节点连接到输出设备
analyser.connect(audioCtx.destination);
isInit = true;
};
- fftSize:设置傅立叶变换的窗口大小,窗口越大,分析的结果越详细。数值必须是2的n次幂
- 分析结果放到数组中,数组的每一项都是一个8位无符号的整数,因此这里创建的不是一个普通数组Array,而是Uint8Array
- frequencyBinCount:这个属性的值为fftSize的一半,因为傅立叶变换后的频域图是对称的结构,所以这里只需要拿到一半的数据即可
最后将分析器节点连接到输出设备,否则无法听到音频声音
绘制数据
随着音频的不断播放,需要把分析器的数据不断的更新到data数组中。
绘制过程就是一些简单的计算逻辑:
每个矩形的宽度 = 画布宽度/数组长度
每个条形的总宽度 = 数据/255 * 画布高度
// 绘制内容
function draw() {
requestAnimationFrame(draw);
// 清空画布
const { width, height } = canvas;
ctx.clearRect(0, 0, width, height);
if (!isInit) return;
// 把分析器节点的数据更新到data中
analyser.getByteFrequencyData(data);
const len = data.length;
const barWidth = width / len;
// 每一个方块的高度
const blockHeight = 8;
for (let index = 0; index < data.length; index++) {
// 拿到本列的数值
const _data = data[index];
const barHeight = (_data / 255) * height;
// 每列的横坐标
const x = index * barWidth;
// 每列的方块数量
const blockCount = Math.round(barHeight / 10);
// 循环绘制每列的小方块
for (let number = 0; number < blockCount; number++) {
// 设置颜色
ctx.fillStyle = gradient[number];
// 每个小方块的纵坐标
const y = height - blockHeight * number;
// 绘制圆角矩形
drawRoundedRect(x, y, barWidth - 1, blockHeight - 1, 2);
}
}
}
绘制圆角矩形
canvas没有直接绘制圆角矩形的方法,我们通过lineTo方法来设置四边,再通过quadraticCurveTo(二次贝塞尔曲线路径)方法来设置圆角的路径,最后再进行填充。
function drawRoundedRect(x, y, width, height, radius) {
if (height === 0) return;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.fill();
}
设置渐变色
HSLA表示一种颜色模式,它是由四个部分组成:色相(Hue)、饱和度(Saturation)、亮度(Lightness)和透明度(Alpha)
- hue(色相):0到360之间的整数,表示颜色的基本属性,如红色、绿色或蓝色。
- saturation(饱和度):0%到100%之间的百分比,表示颜色的纯度。0%表示灰色,100%表示最鲜艳的颜色。
- lightness(亮度):0%到100%之间的百分比,表示颜色的明暗程度。0%表示黑色,50%表示原始颜色,100%表示白色。
- alpha(透明度):0到1之间的小数,表示颜色的透明度。0表示完全透明,1表示完全不透明。
封装一个获取渐变色的方法 generateGradient,接收两个参数:baseColor(起始颜色)、count(渐变色的数量)。
function generateGradient(baseColor, count) {
let hsl = baseColor.match(
/hsla?\((\d+),\s*(\d+%),\s*(\d+%),\s*([\d.]+)\)/
);
let h = parseInt(hsl[1], 10); // Hue
let s = parseInt(hsl[2], 10); // Saturation
let l = parseInt(hsl[3], 10); // Lightness
// 在色盘上按照数量均分,获取每个均分点的颜色
let stepH = 360 / count;
// 提高每个等级的亮度
let stepL = 100 / (count + 1);
let gradientColors = [];
for (let i = 0; i < count; i++) {
gradientColors.push(
`hsl(${h + i * stepH}, ${s}%, ${l + i * stepL}%)`
);
}
return gradientColors;
}
let baseColor = "hsla(240, 100%, 50%, 1)"; // 蓝色
let gradient = generateGradient(baseColor, 200); // 200种颜色
到这里就已经完成了本次案例的全部内容,对于音频的处理这还只是冰山一角,发挥你的想象力可以做出更多可玩性较强的内容。
最后,可以访问 https://resource.dengzhanyong.com/audio/音频可视化.html 查看本次案例的效果。回复 “音频可视化” 获取本案例的全部源码。
往期推荐
10分钟掌握HTML拖放API!让你的网页元素瞬间拥有拖拽功能,轻松提升用户体验!
不要只会用conosle.log了,这几个console命令,让你的调试效率翻倍
前端手写Promise.all,你不知道的多个知识点,比想象中更精彩!
写在最后
欢迎访问我的个人网站:www.dengzhanyong.com
关注我的公众号【前端筱园】,不错过每一篇推送