nes-sim 0.1.4

A NES (Famicom) emulator core library written in pure Rust.
Documentation
use std::cell::RefCell;
use std::rc::Rc;

use crate::apu::ExpansionAudioChip;
use crate::savestate::{SaveStateError, StateReader, StateWriter};

const DUTY_STEPS: u8 = 32;
const DUTY_HIGH: u8 = 16;

const VOLUME_TABLE: [f32; 16] = [
    0.0, 0.007, 0.010, 0.014, 0.019, 0.026, 0.036, 0.049, 0.066, 0.089, 0.119, 0.158, 0.209, 0.275,
    0.360, 0.468,
];

struct SquareChannel {
    enabled: bool,
    period: u16,
    divider: u16,
    step: u8,
}

impl SquareChannel {
    fn new() -> Self {
        Self {
            enabled: true,
            period: 1,
            divider: 0,
            step: 0,
        }
    }

    fn set_period_lo(&mut self, data: u8) {
        self.period = (self.period & 0x0F00) | data as u16;
    }

    fn set_period_hi(&mut self, data: u8) {
        self.period = (self.period & 0x00FF) | (((data & 0x0F) as u16) << 8);
    }

    fn active(&self) -> bool {
        self.enabled && self.period >= 8
    }

    fn tick(&mut self) {
        if !self.active() {
            return;
        }
        self.divider += 1;
        while self.divider >= self.period {
            self.divider -= self.period;
            self.step = (self.step + 1) % DUTY_STEPS;
        }
    }

    fn output(&self) -> f32 {
        if !self.active() {
            return 0.0;
        }
        if self.step < DUTY_HIGH { 1.0 } else { 0.0 }
    }
}

pub(crate) struct Sunsoft5bAudio {
    channels: [SquareChannel; 3],
    volumes: [u8; 3],
    use_envelope: [bool; 3],
    reg_select: u8,
}

impl Sunsoft5bAudio {
    pub(crate) fn new() -> Self {
        Self {
            channels: [
                SquareChannel::new(),
                SquareChannel::new(),
                SquareChannel::new(),
            ],
            volumes: [0; 3],
            use_envelope: [false; 3],
            reg_select: 0,
        }
    }

    pub(crate) fn write_address(&mut self, data: u8) {
        self.reg_select = data & 0x0F;
    }

    pub(crate) fn write_data(&mut self, data: u8) {
        match self.reg_select {
            0x0 => self.channels[0].set_period_lo(data),
            0x1 => self.channels[0].set_period_hi(data),
            0x2 => self.channels[1].set_period_lo(data),
            0x3 => self.channels[1].set_period_hi(data),
            0x4 => self.channels[2].set_period_lo(data),
            0x5 => self.channels[2].set_period_hi(data),
            0x7 => {
                for i in 0..3 {
                    self.channels[i].enabled = (data & (1 << i)) == 0;
                }
            }
            0x8 => {
                self.volumes[0] = data & 0x0F;
                self.use_envelope[0] = (data & 0x10) != 0;
            }
            0x9 => {
                self.volumes[1] = data & 0x0F;
                self.use_envelope[1] = (data & 0x10) != 0;
            }
            0xA => {
                self.volumes[2] = data & 0x0F;
                self.use_envelope[2] = (data & 0x10) != 0;
            }
            _ => {}
        }
    }

    pub(crate) fn tick(&mut self) {
        for ch in &mut self.channels {
            ch.tick();
        }
    }

    pub(crate) fn output(&self) -> f32 {
        let mut out = 0.0;
        for i in 0..3 {
            if self.use_envelope[i] {
                continue;
            }
            out += VOLUME_TABLE[self.volumes[i] as usize] * self.channels[i].output();
        }
        out
    }

    pub(crate) fn save_state(&self, writer: &mut StateWriter) {
        writer.write_u8(self.reg_select);
        for ch in &self.channels {
            writer.write_bool(ch.enabled);
            writer.write_u16(ch.period);
            writer.write_u16(ch.divider);
            writer.write_u8(ch.step);
        }
        for &vol in &self.volumes {
            writer.write_u8(vol);
        }
        for &env in &self.use_envelope {
            writer.write_bool(env);
        }
    }

    pub(crate) fn load_state(
        &mut self,
        reader: &mut StateReader<'_>,
    ) -> Result<(), SaveStateError> {
        self.reg_select = reader.read_u8()?;
        for ch in &mut self.channels {
            ch.enabled = reader.read_bool()?;
            ch.period = reader.read_u16()?;
            ch.divider = reader.read_u16()?;
            ch.step = reader.read_u8()?;
        }
        for vol in &mut self.volumes {
            *vol = reader.read_u8()?;
        }
        for env in &mut self.use_envelope {
            *env = reader.read_bool()?;
        }
        Ok(())
    }
}

pub(crate) struct Sunsoft5bAudioChip {
    audio: Rc<RefCell<Sunsoft5bAudio>>,
}

impl Sunsoft5bAudioChip {
    pub(crate) fn new(audio: Rc<RefCell<Sunsoft5bAudio>>) -> Self {
        Self { audio }
    }
}

impl ExpansionAudioChip for Sunsoft5bAudioChip {
    fn tick_cpu_cycle(&mut self) {
        self.audio.borrow_mut().tick();
    }

    fn output_sample(&self) -> f32 {
        self.audio.borrow().output()
    }
}