Tone.js 实现 Canvas 频谱:十几行代码画出实时音频可视化 Tone.js 实现 Canvas 频谱十几行代码画出实时音频可视化【免费下载链接】Tone.jsA Web Audio framework for making interactive music in the browser.项目地址: https://gitcode.com/gh_mirrors/to/Tone.js给播客页面加一条跟着声音跳动的频谱线是前端很常见的需求。Tone.js 是一个浏览器音频框架把 Web Audio API浏览器底层的音频处理接口封装成现成组件用它做 Web 音频可视化十几行代码就能画出实时跳动的 Canvas 频谱。这篇文章先把能跑的代码端上来再拆开讲数据从哪来、参数怎么调、坑在哪。先跑起来一个播放器、两个分析器、两块画布这段代码做的事很直接播放器循环播音频Tone.FFT和Tone.Waveform两个分析器挂在播放器后面每帧把它们的输出画到两块 Canvas 上。先克隆仓库并构建出产物再把任意一个音频文件放到同目录git clone https://gitcode.com/gh_mirrors/to/Tone.js cd Tone.js npm install npm run build然后保存下面这个index.html用本地静态服务器打开即可!DOCTYPE html html langzh-CN head meta charsetUTF-8 / script src./build/Tone.js/script style canvas { display: block; background: #111; } /style /head body button idtoggle播放 / 停止/button canvas idspectrum width640 height200/canvas canvas idwave width640 height120/canvas script // 播放器换成你本地的音频文件 const player new Tone.Player(kick.wav).toDestination(); // 频谱分析器128 个频率点输出归一化到 0-1 const fft new Tone.FFT({ size: 128, normalRange: true }); // 波形分析器256 个采样点 const wave new Tone.Waveform({ size: 256 }); player.connect(fft); player.connect(wave); const ctxS document.getElementById(spectrum).getContext(2d); const ctxW document.getElementById(wave).getContext(2d); function draw() { // 频谱柱x 轴是频率低到高高度是响度 const bins fft.getValue(); ctxS.clearRect(0, 0, 640, 200); ctxS.fillStyle #3aa0ff; const w 640 / bins.length; bins.forEach((v, i) { const h v * 200; ctxS.fillRect(i * w, 200 - h, w - 1, h); }); // 波形线值域约 -1 到 1映射到画布中部上下 const samples wave.getValue(); ctxW.clearRect(0, 0, 640, 120); ctxW.beginPath(); ctxW.strokeStyle #ff5a4e; ctxW.lineWidth 2; const sw 640 / samples.length; samples.forEach((v, i) { const y (v 1) / 2 * 120; i 0 ? ctxW.moveTo(0, y) : ctxW.lineTo(i * sw, y); }); ctxW.stroke(); requestAnimationFrame(draw); } draw(); // 浏览器禁止自动播音频必须先有用户手势 document.getElementById(toggle).onclick async () { await Tone.start(); if (player.state started) player.stop(); else player.start(); }; /script /body /html点一下按钮上面的频谱柱和下面的波形线就开始动了。按钮没点之前频谱是平的这是正常的——音频时钟还没跑分析器拿到的就是零。拆组件三个分析器各返回什么三个分析器都在 Tone/component/analysis/ 目录下共同点是先connect到任意音源再调getValue()读数。Tone.FFT快速傅里叶变换把声音拆成一堆不同音高的正弦波再看每根多响返回长度为size的数组每个元素是一个频段的幅度低频在前、高频在后。天然适合画柱状频谱。默认normalRange是false此时读出来是分贝值负数设成true才直接给 0-1画图省事。Tone.Waveform返回时域采样数组长度也是size数值在 -1 到 1 之间对应信号在某一时刻的幅度。适合画连续的波形折线。Tone.Meter只返回当前输入响度RMS均方根一种衡量平均多响的算法normalRange: true时是 0-1 的单值。只想画一根音量条、或者声音大时亮个图标用它就够了不必去取整条频谱。三者本质上是同一个Analyser的三种取值模式所以参数行为一致size都必须是 2 的幂范围 16 到 16384默认 1024。调参 size 不是越大越好看新手最容易犯的错是觉得数据点多 更精细。实际上点数多只是多画几笔细节并不更多。给几个可以直接抄的数值频谱柱只打算画 30-60 根的话Tone.FFT的size用 128 完全够开到 1024 只是白白多画 900 多根细柱。波形线画在 640 像素宽的画布上size取 256 到 512 就很顺眼2048 以上肉眼看不出区别GPU 和主线程替你加班。Tone.FFT的smoothing默认 0.8是和上一帧的平滑系数。想要柱子稳、不抖提到 0.9想要跟手、有弹性降到 0.5 左右。每帧点数 size的绘制调用次数这是帧率的直接开销。掉帧时先砍size再考虑别的手段。让绘制跟着音频时钟走连续跳动的频谱requestAnimationFrame循环就够了别用setInterval定速——它和显示刷新不对齐画面会轻微抖动。但如果你要做的是视觉事件精确落在某个音乐时刻比如底鼓一下、方块闪一下requestAnimationFrame和 Transport 的回调都靠不住调度回调触发得比实际时间早又不对齐显示帧。这时候用Tone.Draw。它底层也是requestAnimationFrame但按 AudioContext 时钟浏览器里所有音频事件的统一时间轴排期回调见 Tone/core/util/Draw.ts。简单记画持续动画用 rAF画踩点事件用Tone.Draw。视觉打磨渐变和阴影怎么加渐变只创建一次别每帧createLinearGradient。建好gradient后直接赋给fillStyle柱子立刻有层次。阴影同理shadowBlur、shadowColor在循环外设一次。注意阴影是逐元素合成的柱子多、开大阴影时帧率会明显下降移动端尤其明显。波形线画之前先描一条淡色的水平中线当基准线波形上下跳动的参照感会强很多。深色背景#111左右对频谱最友好浅色背景下建议换高饱和度的填充色否则蓝色柱子几乎看不见。踩坑速查 ⚠️点按钮没声音浏览器禁止页面自行开播音频必须在用户手势里await Tone.start()之后再player.start()。频谱柱永远是 0 或负数normalRange默认falsegetValue()返回的是分贝负数。要么设normalRange: true要么自己把分贝映射到 0-1 再画。画面有残影、闪烁漏了每帧开头的clearRect。想要拖尾效果可以用半透明色块覆盖代替清屏那是另一种画法要刻意做。帧率上不去先查size是不是开到了 1024 以上砍到 128/256 通常立竿见影。实战延伸麦克风、效果器、移动端麦克风输入用 Tone/source/UserMedia.ts 里的Tone.UserMedia替换Tone.Player接到同一组分析器上即可注意开麦克风只支持 HTTPS 页面。和效果器联动把Tone.Distortion、Tone.Reverb这类效果器串在玩家和分析器之间画面画的就是处理后的信号声音变了画面自然跟着变。移动端适配iOS Safari 上 AudioContext 的启动和暂停行为有差异仓库 Tone/core/context/ 下就是做这层适配的代码遇到怪现象先翻这里。收个尾Tone.js 把取音频数据这件事压成了两个组件加一句connect剩下的是你 Canvas 里怎么画、画多少点。仓库 examples/ 目录里有三十多个现成页面其中的analysis.html就是本文写法的官方版本改几行就能抄走。【免费下载链接】Tone.jsA Web Audio framework for making interactive music in the browser.项目地址: https://gitcode.com/gh_mirrors/to/Tone.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考