数组值如何变成声波?Visual Sorting的Web Audio声音引擎实现深度剖析
【免费下载链接】visual-sorting🔮 🔈 Visual Sorting (aka "The Sound Of Sorting") is a tool that provides a visualization of sorting algorithms, accompanied by an auditory experience.项目地址: https://gitcode.com/gh_mirrors/vi/visual-sorting
Visual Sorting(又名 The Sound Of Sorting)是一款排序算法可视化工具:它把数组中的每个值画成一根柱子,并让算法"碰"到的柱子发出对应音高——值越大,声音越尖。整副"声音魔法"其实只用了不到 50 行基于 Web Audio API 的代码,本文带你拆解这套声音引擎的完整实现原理。
一、为什么排序可以"听起来"?
传统教学里,我们靠眼睛观察柱子的移动来理解排序过程。而 Visual Sorting 借鉴了经典视频The Sound of Sorting的创意:把数组值映射到音频频率,让耳朵参与观察。
这样做的直观好处是:
- 🎵 听到连续低音符,说明算法在处理小区间;
- 📈 听到"刮玻璃"式的高频滑音,说明它在跨大步长扫描;
- 🏁 排序快结束时,音符逐渐单调上扬——你"听"到了数组变有序。
整个功能由一条清晰的数据流驱动,下面逐层拆解。
二、声音引擎本体:只有 50 行的 sound.ts
核心实现全部集中在 sound.ts 中,对外只暴露 3 个函数:
| 函数 | 职责 | 关键代码位置 |
|---|---|---|
soundStart(size, name) | 启动振荡器,计算频率步长 | sound.ts#L17-L30 |
soundStop() | 停止并断开振荡器 | sound.ts#L32-L43 |
playValue(value) | 把数组值换算成频率并即时发声 | sound.ts#L45-L50 |
引擎设计上有两个要点:
- 全局单例
AudioContext。浏览器限制同一页面并发 AudioContext 数量,且排序全程只需要一条音频流,因此 sound.ts#L9 在模块加载时就用new AudioContext()创建唯一上下文,并仅在浏览器环境创建(SSR 渲染时自动跳过)。 - 一个持续运转的振荡器,而不是每个值建一个新振荡器。如果每次访问数组元素都新建振荡器,会产生大量杂音和性能开销;这里采用"长鸣 + 改频率"策略——振荡器一直响,每次只改写它的
frequency参数。
三、值到频率:一条线性映射公式
"数组值如何变成声波"的答案,就藏在两个常量与一行公式里。
1. 划定可听频段(sound.ts#L14-L15):
minFrequency = 10 Hz:最低音下限maxFrequency = 1500 Hz:最高音上限
选择 1500 Hz 而不是更高,是为了把音调控制在人耳对频率差异分辨力最好的中低频段,避免尖锐刺耳。
2. 启动时计算步长(sound.ts#L25):
freqStepSize = maxFrequency / size以默认 300 根柱子为例,步长约为 5 Hz——每增大 1 个数组值,音高上升约 5 Hz。
3. 发声时的映射(sound.ts#L49):
frequency = freqStepSize × (value − 1) + minFrequency这就是一条从[1, size]到[10 Hz, 1500 Hz]的线性映射:数组里的最小值 1 发出 10 Hz 的低沉嗡鸣,最大值size发出接近 1500 Hz 的高音。柱子越高,音调越高,视觉与听觉在此刻完全对齐。
四、算法如何"告诉"引擎该发什么音
如果引擎只负责发音,那么"什么时候发、发什么音"由谁决定?答案是排序算法本身。
每个算法(如 bubble-sort.ts)都是一个 JavaScript 生成器函数,每一步yield一个 ProgressIndicator 结构:
access:本步骤访问了哪些下标(用于界面高亮);sound:哪根柱子应该发声(关键!);comparisons / swaps / accesses:统计增量(顶部实时指标)。
以冒泡排序为例,每次比较arr[j]与arr[j+1]时都会yield出sound: j + 1(bubble-sort.ts#L17-L23),意思是"把j+1位置柱子的值念出来"。
SortPanel.svelte 的tick()方法每走一步就检查这个字段,若存在且当前有音色启用,便调用playValue(workingArray[next.value.sound])(SortPanel.svelte#L55-L61)。
这一层解耦非常漂亮:算法只声明"语义"(我碰了哪个位置),引擎负责"物理"(换算频率、驱动扬声器)。想给新算法加声音,只需在 yield 里带上sound字段,一行都不用改音频代码。
五、24 种音色:web-audio-oscillators 与界面控制
"值→频率"解决的是音调,而音色(正弦、方波、锯齿……)则由开源库web-audio-oscillators提供(见 package.json)。
- 界面右上角的音色下拉菜单(HeaderSoundControl.svelte)直接遍历库导出的
customOscillatorTypes,共24 种振荡器可选,无需手写任何波形代码; - 选择结果通过
savePref持久化到本地存储,刷新页面后依然保留,默认为triangle(三角波); - 按键盘
M可一键静音——静音的实现其实就是把oscillatorType置为null,引擎会自动停止振荡器。
还有一个细节:在对比模式(两个算法同屏赛跑)下,只有 A 面板发声。因为全站共享唯一的 AudioContext,两个振荡器同时写入会互相"打架"产生和声干扰(参见 +page.svelte#L218-L222 中的注释说明)。
六、几个值得一提的工程细节
- 双停止保护:Web Audio 对已停止的振荡器再次调用
stop()会抛InvalidStateError,soundStop()用try/catch兜底(sound.ts#L36-L40),保证 Start/Stop 快速连点也不会报错。 - 启动即清理:
soundStart内部先执行soundStop()(sound.ts#L26),避免新旧振荡器叠加。 - 单步模式延时关闭:点击 Step 单步执行时,声音会在 100 ms 后自动停止(+page.svelte#L248-L252),让每一声短促清脆,便于逐步分析。
- 响应式开关:主页面用 Svelte 响应式语句
$running && oscillatorType ? soundStart(...) : soundStop()(+page.svelte#L208-L210),排序状态一变,音频自动跟着启停。
七、动手体验
如果想亲自调试这套声音引擎,克隆仓库后本地启动即可:
git clone https://gitcode.com/gh_mirrors/vi/visual-sorting cd visual-sorting npm install npm run dev打开页面后建议试试:选Bubble Sort+None/Triangle音色对比、把 Array size 调小(如 30)放慢速度,你能清晰地"听"出每次比较落在哪根柱子上——这正是这套 50 行 Web Audio 声音引擎想要传达的:把抽象的数组操作,翻译成耳朵也能读懂的信号。
【免费下载链接】visual-sorting🔮 🔈 Visual Sorting (aka "The Sound Of Sorting") is a tool that provides a visualization of sorting algorithms, accompanied by an auditory experience.项目地址: https://gitcode.com/gh_mirrors/vi/visual-sorting
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考