NES APU 可视化

如果无法看到演示界面,可以点击这里检验,也可以点击这里 查看演示效果与代码。

使用说明

  • 将录制好的文本文件拖放到 UI 中即可开始播放
  • 录制文件格式如下:
    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
    
    其中,第一列表示 CPU 时钟周期;第二列表示寄存器地址;第三列表示写入寄存器的数据
  • 录制文件可由其它模拟器录制产生。使用 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() 也没有任何效果。

posted @ 2023-03-21 18:43  1bite  阅读(78)  评论(0编辑  收藏  举报