GB/音频/序列发生器

序列器发生器(Frame Sequencer)为调制单元生成低频时钟. 它由 512 Hz 的定时器提供时钟控制. 序列发生器间接接受 CPU 时钟输入, 它有点类似于一个入口函数, 基本上音频模块的所有逻辑都经由它的运作而触发.

序列器发生器在不同时间分片下触发不同的硬件单元:

Step   Length Ctr  Vol Env     Sweep
---------------------------------------
0      Clock       -           -
1      -           -           -
2      Clock       -           Clock
3      -           -           -
4      Clock       -           -
5      -           -           -
6      Clock       -           Clock
7      -           Clock       -
---------------------------------------
Rate   256 Hz      64 Hz       128 Hz

由上表可知长度计数器(Length Ctr)的频率时 256 Hz, 音量包络(Vol Env)的频率是 64 Hz, 扫频器(Sweep)的频率是 128 Hz.

序列发生器的实现也非常简单, 基本上可看作一个周期固定为 8 的特殊定时器.

struct FrameSequencer {
    step: u8,
}

impl FrameSequencer {
    fn power_up() -> Self {
        Self { step: 0x00 }
    }

    fn next(&mut self) -> u8 {
        self.step += 1;
        self.step %= 8;
        self.step
    }
}

最后将 FrameSequencer 接入到 512 Hz 时钟信号, 并控制长度计数器, 音量包络和扫频器在 4 个音频通道中的运行. 注意目前并没有真的去实现这些逻辑硬件的代码, 它们仅仅在此处被占位.

impl Apu {
    pub fn next(&mut self, cycles: u32) {
        if !self.reg.get_power() {
            return;
        }

        for _ in 0..self.timer.next(cycles) {
            self.channel1.next(self.timer.period);
            self.channel2.next(self.timer.period);
            self.channel3.next(self.timer.period);
            self.channel4.next(self.timer.period);

            // 512 Hz 时钟信号
            //
            // ... Your Codes
            let step = self.fs.next();
            if step == 0 || step == 2 || step == 4 || step == 6 {
                // Length Ctr
                self.channel1.lc.next();
                self.channel2.lc.next();
                self.channel3.lc.next();
                self.channel4.lc.next();
            }
            if step == 7 {
                // Vol Env
                self.channel1.ve.next();
                self.channel2.ve.next();
                self.channel4.ve.next();
            }
            if step == 2 || step == 6 {
                // Sweep
                self.channel1.fs.next();
            }
        }
    }
}