NES APU 可视化
如果无法看到演示界面,可以点击这里检验,也可以点击这里 查看演示效果与代码。
使用说明:
- 将录制好的文本文件拖放到 UI 中即可开始播放
- 录制文件格式如下:
其中,第一列表示 CPU 时钟周期;第二列表示寄存器地址;第三列表示写入寄存器的数据82946, $4015, $0f 82952, $4017, $c0 83145, $4015, $0b 83151, $4008, $00 83157, $4015, $0f 83169, $4000, $30 83205, $4004, $30 83241, $400c, $30 83279, $4001, $7f 83315, $4005, $7f
- 录制文件可由其它模拟器录制产生。使用 Mesen 录制时,可以使用如下录制条件:
IsWrite && ((Address >= $4000 && Address <= $400f) || Address == $4015 || Address == $4017)
- UI 从上到下共 5 行。分别代表:方波通道一、方波通道二、三角波通道、噪声通道、最终合成效果
- UI 从左到右共 5 列。分别代表:波形、通道基本信息、滑音单元、包络生成器、长度计数器
实现细节
APU 部分可以参考 NES APU 这篇文章,不赘述
音频部分当然是采用 WebAudio API,基本思路就是每次产生一小段样本数据,然后送去播放。收到播放完毕通知后,再产生,再播放,这样一直循环。不过,这里有几个小细节。
一是播放完毕的通知存在延迟且时长不固定。如果收到通知再产生下个音频数据块,会让整体声音有停顿,听起来极度不适。解决此问题可维护一个音频数据块列表,这个列表不用太大,比如长度为 5 即可。设每个音频数据块的播放时长是 20 ms, 则这个列表中的音频数据全部播放完毕共需要 100 ms。每个块儿播放完毕后,会有一个播放完毕通知。因此,不用等 100 ms 就可以收到通知,此时列表内应还剩音频数据还没有播放完毕。收到通知后,先移除掉已经播放过的块,再产生音频数据,直到列表填满。由于此时列表还有剩余音频没有播放完毕,而新的音频数据已经就绪,就不会出现停顿。这个方法有个前提假设,就是通知的延迟不能太高。按列表长度为 5 来算,延迟不能大于 80 ms。就实际情况来看,延迟大约在 20 ms 左右的样子。
二是 AudioBufferSourceNode
没有队列功能。起初我以为跟 Windows API 是有列队功能的,结果导致列表内的音频数据实际上是同时播放了,听起来也是极度不适。这里应该采用 AudioBufferSourceNode.start(startTime)
指定每个块的开始时间,避免同时播放的问题。设每个块儿播放时长是 20 ms,则每个块的开始时间就是上个块的开始时间再加 20 ms。第一个块的播放时间应该是 AudioContext.currentTime
,开始时间小于 AudioContext.currentTime
的音频数据也是会立即播放的!这里,第一个块是指第一个被播放的数据块,不是上述列表中的第一个元素。
三是如果用户没有在页面上进行过用户交互,则 AudioContext.state
的初始状态是 suspended
,此时即使调用 AudioContext.resume()
也没有任何效果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)